Implement persistent foreground service to keep calls active in background, with notification controls for managing the call by tareko · Pull Request #5660 · nextcloud/talk-android

…round, with notification controls for managing the call

- Add CallForegroundService with persistent notification
- Support calls in background without requiring picture-in-picture mode
- Add "Return to call" and "End call" action buttons to CallForegroundService notification with corresponding PendingIntent
- Handle proper foreground service types for microphone/camera permissions
- Add notification permission and fallback messaging.
- Add EndCallReceiver to handle end call broadcasts from notification action
- Use existing ic_baseline_close_24 drawable for end call action icon
- Register broadcast receiver in CallActivity to handle end call requests from notification using ReceiverFlag.NotExported for Android 14+ compatibility
- Add proper cleanup flow: notification action → EndCallReceiver → CallActivity → proper hangup sequence
- Track intentional call leaving to prevent unwanted service restarts
- Release proximity sensor lock properly during notification-triggered hangup
- Add diagnostic logging throughout the end call flow for debugging

The implementation follows Android best practices:
- Uses NotExported receiver flag for internal app-only broadcasts
- Properly unregisters receivers in onDestroy to prevent leaks
- Uses immutable PendingIntents for security
- Maintains proper state management during call termination

Signed-off-by: Tarek Loubani <tarek@tarek.org>
…tyle

Signed-off-by: Tarek Loubani <tarek@tarek.org>

mahibi

sowjanyakch

Tarek Loubani added 7 commits

March 26, 2026 02:39
…do a rapid gesture switch back.

For the quick-switch gesture and the recents-to-chat path, the OS starts animating the transition before onPause() fires. By the time onPause()
runs, the window has already been moved off-screen by the gesture animation, so enterPictureInPictureMode() silently fails — Android requires the
window to still be visible.

Why onTopResumedActivityChanged(false) fixes it:
This callback fires when any other activity (including ChatActivity in the same app) takes the "top resumed" slot. Critically, it fires before
onPause() and before any transition animation begins — the window is still fully on-screen. enterPictureInPictureMode() succeeds at this point.

Why back-button worked but this didn't:
Back button goes through OnBackPressedCallback.handleOnBackPressed() synchronously, which calls enterPipMode() before any transition, not in a
lifecycle callback. onTopResumedActivityChanged puts the task-switch path on the same footing.

API compatibility: On API 26–28, onTopResumedActivityChanged is never called by the system (it didn't exist in Activity before API 29), so
onPause() remains the fallback. Older devices primarily use button navigation and won't have the gesture quick-switch anyway.
Use observeForever for leaveRoom observer so cleanup runs even when
activity is paused. Move ApplicationWideCurrentRoomHolder.clear() into
the leave success callback to avoid premature state clearing. Guard
against double leaveRoom calls with isLeavingRoom flag.

Tarek Loubani added 3 commits

April 2, 2026 04:32
…c logging

- Prevent spurious roomJoined events from re-running performCall() when
  already IN_CONVERSATION, fixing call reconnection when ChatActivity
  resumes behind PIP or task switch
- Remove setAutoEnterEnabled(true) which conflicts with manual
  enterPictureInPictureMode() calls causing invisible PIP windows
- Set aspect ratio in initial PIP params (onCreate) so PIP params are
  always valid
- Add isInPipMode guard to onUserLeaveHint to prevent redundant PIP
  entry attempts
- Add diagnostic logging to CallBaseActivity lifecycle methods and
  CallActivity PIP/call state transitions
- Add unit tests documenting PIP race conditions and leaveRoom
  lifecycle behavior
…tivityChanged fallback

The previous approach had three competing PIP entry mechanisms on API 31+
(auto-enter, onTopResumedActivityChanged, onPause) that raced against each
other, and onTopResumedActivityChanged toggled setAutoEnterEnabled off/on
which broke smooth transitions.

New layered approach per the Android PIP documentation:
- API 31+: setAutoEnterEnabled(true) as primary for home/recents gestures
- API 29+: onTopResumedActivityChanged as fallback (fires while window is
  still visible, catches quick-switch gestures auto-enter misses)
- API 26-30: onUserLeaveHint for home/recents, onPause fallback for 26-28
- All APIs: OnBackPressedCallback for back gesture (only manual entry point)

Key fix: onTopResumedActivityChanged no longer disables auto-enter. It checks
isInPictureInPictureMode() so if auto-enter already handled it, the manual
call is skipped. No races, no toggling.

Also removes shouldFinishOnStop, pipFallbackHandler, topResumedLostTime, and
onCreateTime which were artifacts of the old racing approach.
Remove excludeFromRecents=true from CallActivity manifest entry. This
attribute caused Android to destroy the entire call task ~5s after the
user navigated away via task switch, killing the call.

Guard all teardown in onDestroy (signaling listeners, localStream,
foreground service, proximity sensor, broadcast receiver) so that
system-initiated destruction during task switching doesn't tear down
active call resources. The foreground service keeps the process alive.

Simplify onTopResumedActivityChanged to only enter PIP on API 29-30.
On API 31+, auto-enter handles swipe-up; onUserLeaveHint moves the task
to back as a safety net for task switching.