Skip to content

fix(android): use fromBundleOrNull in PhoneConnectionService.onStartCommand#301

Open
martin-ez wants to merge 2 commits into
WebTrit:mainfrom
slingshot-ai:seb/upstream-fix-onstartcommand-missing-callid
Open

fix(android): use fromBundleOrNull in PhoneConnectionService.onStartCommand#301
martin-ez wants to merge 2 commits into
WebTrit:mainfrom
slingshot-ai:seb/upstream-fix-onstartcommand-missing-callid

Conversation

@martin-ez

@martin-ez martin-ez commented May 27, 2026

Copy link
Copy Markdown

Summary

PhoneConnectionService.onStartCommand calls CallMetadata.fromBundle(it) on the intent extras. fromBundle throws IllegalArgumentException("Missing required callId property in Bundle") when callId is absent.

But several ServiceAction cases handled immediately below — TearDownConnections, CleanConnections, SyncAudioState, SyncConnectionState — don't carry a callId in their extras. And the actions that do (ReserveAnswer, NotifyPending) already null-check metadata?.callId before using it (lines 116-122).

The throw at the parse step makes those defensive null-checks unreachable: the foreground service crashes every time the host app dispatches one of the no-callId actions.

This shows up in our Play Vitals as IllegalArgumentException originating from CallMetadata\$Companion.fromBundle at CallMetaData.kt:121, triggered from PhoneConnectionService.onStartCommand at PhoneConnectionService.kt:103.

Fix

Use fromBundleOrNull so missing-callId bundles produce metadata == null, which the existing handlers already expect.

martin-ez added 2 commits May 27, 2026 20:03
…ommand

`CallMetadata.fromBundle` throws `IllegalArgumentException("Missing
required callId property in Bundle")` when the bundle has no callId.
But several `ServiceAction` cases handled in `onStartCommand` don't
carry a callId in their extras (`TearDownConnections`,
`CleanConnections`, `SyncAudioState`, `SyncConnectionState`), and the
ones that do already null-check `metadata?.callId` before using it.
The throw at line 103 makes the defensive null-checks below
unreachable, and crashes the foreground service every time the host
app triggers a teardown or sync action.

Switch to `fromBundleOrNull` so missing-callId bundles produce a null
metadata that the existing handlers already expect.
SERDUN added a commit that referenced this pull request Jun 14, 2026
#319)

* fix: parse service intents into typed commands to avoid metadata crash

Both PhoneConnectionService and StandaloneCallService eagerly parsed call
metadata via CallMetadata.fromBundle at the top of onStartCommand, BEFORE the
surrounding try/catch. Binder IPC can deliver a non-null but empty Bundle for
the no-extras lifecycle commands (TearDownConnections, CleanConnections,
SyncAudioState, SyncConnectionState), so the missing-callId
IllegalArgumentException propagated uncaught out of onStartCommand and crashed
the :callkeep_core process (confirmed in Play Vitals).

Introduce PhoneServiceCommand / StandaloneServiceCommand sealed types with a
from(intent) factory that parses the action and metadata once. Lifecycle
commands carry no metadata and never touch the extras; Reserve/Pending carry a
non-null callId; AddIncoming/Call carry non-null metadata. onStartCommand
switches over the typed command, so metadata parsing only runs for actions that
need it and any parse failure is non-fatal.

This supersedes PR #301, which only switched PhoneConnectionService to
fromBundleOrNull and left StandaloneCallService with the same latent crash.

WT-1142

* fix: promote standalone call-setup to foreground before command parse

When StandaloneServiceCommand.from() returned null (a call-setup intent with
empty/truncated Binder extras), onStartCommand bailed out before
promoteToForeground(). IncomingCall/OutgoingCall are started via
startForegroundService, so skipping startForeground() crashes the process with
ForegroundServiceDidNotStartInTimeException ~5s later.

Decide the foreground promote from the raw StandaloneServiceAction (new
isCallSetup predicate on the enum) BEFORE the command-parse bail-out, so the
5s window is satisfied even when metadata fails to parse. On the bail-out path,
call the extracted stopIfIdle() so a promoted-but-unparsed call-setup intent
does not linger as a zombie foreground service. Drop the now-unused isCallSetup
property from StandaloneServiceCommand.

WT-1142

* fix: reject connection on missing callId instead of crashing

onCreateOutgoingConnection and onCreateIncomingConnection parsed metadata with
CallMetadata.fromBundle, which throws IllegalArgumentException when callId is
absent. request.extras comes from our own metadata.toBundle(), so a missing
callId is a "should never happen" invariant violation (Binder truncation,
process death/recovery, stale framework callback) -- but the uncaught throw
would crash the whole :callkeep_core process, the same crash class fixed in
onStartCommand.

Use fromBundleOrNull and, on null, log an error and return a failed Connection
(DisconnectCause.ERROR) so the framework rejects this one connection while the
process stays alive.

WT-1142

* fix: log distinct reason when a service command is ignored

The command factory returns null both for an unrecognised action and for a
known action missing its required callId/metadata, and onStartCommand logged a
single generic message for both. Branch the log on whether the action resolves
to a known enum value, restoring the diagnostic specificity (known-action
missing field vs unknown action) lost in the ServiceCommand refactor.

WT-1142
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant