Android native calling — Increment 1: full-screen incoming ring + answer

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>
This commit is contained in:
SravanandClaude Opus 4.8 committed 2026-09-25 11:44:52 +05:30
1 parent a8e0f549d4
commit b60ce6cda3
12 files changed
+714 -18

No files matched your search

+27
View File
@@ -15,6 +15,33 @@ const p = process.argv[2];
if (!p || !fs.existsSync(p)) { console.log(' (AndroidManifest.xml not found — permission patch skipped)'); process.exit(0); }
try {
let s = fs.readFileSync(p, 'utf8');
// (1) Native-call FirebaseMessagingService swap. The native-call plugin declares its own MESSAGING_EVENT
// service (BzCallMessagingService) that handles call-wake data messages AND forwards regular push to Capacitor
// via super. Remove @capacitor/push-notifications' own MessagingService here — the APP manifest is the highest
// merge priority, so this reliably guarantees exactly one receiver. Needs the `tools` namespace + an insert
// inside <application>. Keyed on bzcCallSvcRemove so re-runs never duplicate it.
if (!s.includes('bzcCallSvcRemove')) {
// Ensure xmlns:tools is declared on <manifest> (tools:node="remove" needs it).
if (!/<manifest[^>]*xmlns:tools=/.test(s)) {
s = s.replace(/<manifest(\s)/, '<manifest xmlns:tools="http://schemas.android.com/tools"$1');
}
const removal = [
'',
' <!-- bzcCallSvcRemove: the native-call plugin\'s BzCallMessagingService replaces this one (it forwards regular push via super) -->',
' <service android:name="com.capacitorjs.plugins.pushnotifications.MessagingService" tools:node="remove" />',
' ',
].join('\n');
const before = s;
s = s.replace(/<\/application>/, removal + '</application>');
if (s !== before) console.log(' Capacitor MessagingService removal injected (native-call takes over MESSAGING_EVENT)');
else console.log(' could not find </application> — MessagingService removal skipped');
fs.writeFileSync(p, s);
} else {
console.log(' Capacitor MessagingService removal already present');
}
// (2) WebView permissions.
if (s.includes('bzcAndroidPerms') || s.includes('android.permission.RECORD_AUDIO')) {
console.log(' Android permissions already present'); process.exit(0);
}