// Keep an Android foreground service alive while a DM call is actively joined. // // Lifecycle is keyed to the widget's JoinCall signal (via useCallJoined), not // the mere presence of callEmbedAtom. Rationale: // // 1. RECORD_AUDIO runtime grant. By the time JoinCall fires, Element Call // inside the iframe has already called getUserMedia, which prompted the // OS permission dialog (if needed) and user granted. Starting the FGS // earlier (during `preparing`, before getUserMedia) means RECORD_AUDIO // may not be granted yet — and on API 34+ startForeground with // TYPE_MICROPHONE throws SecurityException without it, while a // fallback to TYPE_NONE is not a valid subset of the manifest-declared // `microphone` service type. Gating on joined sidesteps the whole // fallback question. // // 2. No ghost notification during preparingError. If the widget fails to // load, JoinCall never fires, the service never starts, nothing to // clean up. // // Tradeoff: a tiny (1-3s) window between embed creation and JoinCall has no // retention. The call isn't actually live in that window — media hasn't // started — so lock-screen there is a non-event; user can retry. // // Android-only. import { useEffect } from 'react'; import { useAtomValue } from 'jotai'; import { callEmbedAtom } from '../state/callEmbed'; import { callForegroundService } from '../plugins/call/callForegroundService'; import { useCallJoined } from './useCallEmbed'; import { isAndroidPlatform } from '../utils/capacitor'; export const useAndroidCallForegroundSync = (): void => { const callEmbed = useAtomValue(callEmbedAtom); const joined = useCallJoined(callEmbed); useEffect(() => { if (!isAndroidPlatform()) return undefined; if (!joined) return undefined; callForegroundService.start({ title: 'Активный звонок' }).catch((err: unknown) => { // eslint-disable-next-line no-console console.warn('[call-fgs] start failed', err); }); return () => { callForegroundService.stop().catch((err: unknown) => { // eslint-disable-next-line no-console console.warn('[call-fgs] stop failed', err); }); }; }, [joined]); };