CS & IMS Call Flows
How an Android phone places and receives a voice call, class by class: from the Dialer through Telecom and Telephony down to the RIL and modem for circuit-switched (CS) calls, and down to the ImsService and SIP for IMS calls (VoLTE, VoNR, VoWiFi). This is the single most common "trace it end to end" question in telephony interviews, and the map you need for debugging failed or dropped calls.
- Every call, CS or IMS, takes the same top path: Dialer →
TelecomManager→ Telecom (CallsManagerin system_server) →TelephonyConnectionService(incom.android.phone) →Phone.dial(). - The CS-vs-IMS fork is in
GsmCdmaPhone.dial(): ifuseImsForCall()is true it delegates toImsPhone, otherwiseGsmCdmaCallTracker→RIL→IRadioVoice.dial→ modem. - IMS calls go
ImsPhoneCallTracker→ImsCall→IImsCallSession→ vendorMmTelFeature/ImsService → SIPINVITE; voice media is RTP on a QCI-1 / 5QI-1 bearer. - State comes back up the same path: modem/IMS event → call tracker →
TelephonyConnection.setActive()→ Telecom →InCallService(the in-call UI). - Everything above the radio HAL / ImsService is AOSP and identical on MediaTek and Qualcomm; the vendor split starts at rild / the IMS stack.
CS vs IMS: the big picture
Android supports two fundamentally different ways of carrying a voice call. A circuit-switched (CS) call reserves a dedicated channel in the 2G/3G CS domain for the whole call. An IMS call is a SIP session over an IP bearer; voice is carried as RTP packets. VoLTE (over LTE), VoNR (over 5G SA) and VoWiFi (over Wi-Fi via an ePDG tunnel) are all IMS calls; they differ only in the access network.
A CS call is like booking a private train carriage: the track and seats are reserved for you from departure to arrival, whether you talk or not. An IMS call is like sending your conversation as a stream of parcels on a busy motorway, but with a guaranteed express lane. In the real system, the "private carriage" is the CS radio access bearer set up by the modem's MM/CC layers, and the "express lane" is the QCI-1 (LTE) or 5QI-1 (NR) guaranteed-bit-rate bearer that carries the RTP voice packets.
CS (circuit-switched) call
- Voice on a dedicated circuit in the CS domain (GSM/UMTS, or LTE via CS fallback).
- Android path:
GsmCdmaPhone→GsmCdmaCallTracker→RIL.dial()→IRadioVoice.dial→ modem. - Over the air: 3GPP TS 24.008 Call Control:
SETUP→CALL PROCEEDING→ALERTING→CONNECT. - Used when IMS is not registered or not allowed, on 2G/3G-only networks, via CSFB from LTE, and for some emergency calls.
IMS call (VoLTE / VoNR / VoWiFi)
- Voice is a SIP session plus RTP media over IP (SIP on QCI-5, voice on QCI-1).
- Android path:
ImsPhone→ImsPhoneCallTracker→ImsCall→ vendorImsService/MmTelFeature→ SIP stack. - Over the network: SIP
INVITE→100→183→PRACK→UPDATE→180→200 OK→ACK. - Used when IMS is registered and VoLTE/VoNR/WFC is enabled and provisioned (CarrierConfig + user setting).
The one decision point
Both paths share everything above the Phone object. The fork is inside GsmCdmaPhone.dial(): if IMS is usable for this call it delegates to its child ImsPhone; otherwise it dials on CS through GsmCdmaCallTracker. If the IMS attempt fails in a way that says "retry on CS", the framework silently redials on CS (see the CS fallback section).
Dialer / any calling app
│ TelecomManager.placeCall()
▼
Telecom (system_server) : CallsManager
│ bind ConnectionService
▼
TelephonyConnectionService (com.android.phone)
│
▼
GsmCdmaPhone.dial()
┌─────────────────┴─────────────────┐
useImsForCall() == true useImsForCall() == false
│ │
ImsPhone.dial() GsmCdmaCallTracker.dial()
ImsPhoneCallTracker RIL.dial()
ImsCall / IImsCallSession IRadioVoice.dial (HAL)
vendor ImsService (MmTelFeature) rild → modem CC/MM
SIP INVITE + RTP (QCI-1) CS SETUP → CONNECT
com.android.phone), the three Binder boundaries, and the exact fork point (GsmCdmaPhone.dial() / useImsForCall()). Weak answers jump straight from "Dialer" to "modem".Full-stack sequence: Dialer to modem and back to the UI
One mobile-originated (MO) CS call as it crosses every process boundary: Dialer (app) → system_server (Telecom) → phone process (com.android.phone: TelephonyConnectionService, GsmCdmaPhone, call tracker, RIL Java) → vendor rild + IRadioVoice HAL → modem. The request goes down once; everything afterwards is the modem reporting state up and the framework pushing each transition to the UI.
It is like ordering food through an app. You press "order" once (the downstream request). After that you only receive status updates: "restaurant accepted", "rider on the way", "delivered". You never ask again; the kitchen pushes updates and the app redraws. In the real system the "order" is RIL.dial(), the "accepted" message is dialResponse, and each status update is an unsolicited callStateChanged that makes the call tracker re-poll the call list and push dialing → alerting → active up to the InCallService.
Dialer system_server com.android.phone rild + IRadio HAL Modem (InCallUI) (Telecom) (TCS/Phone/Tracker/RILJ) (vendor) (baseband) │ │ │ │ │ │ ① ORIGINATION (downstream) │ │ │ │ 1 placeCall() ─►│ │ │ │ │ │ 2 CallsManager.startOutgoingCall: pick PhoneAccount, new Call │ │ │ 3 createConnection() ─►│ (IConnectionService Binder) │ │ │ │ 4 TCS.onCreateOutgoingConnection │ │ │ │ 5 GsmCdmaPhone.dial(): useImsForCall()? no │ │ │ │ GsmCdmaCallTracker.dial() │ │ │ │ new GsmCdmaConnection (DIALING) │ │ │ │ 6 RIL.dial() ─────────►│ IRadioVoice.dial │ │ │ │ │ 7 QMI VOICE dial / │ │ │ │ │ AT: ATD<num>; ─►│ │ ② MODEM ACCEPTS AND REPORTS (upstream) │ │ │ │ │ │ │◄─ 8 accepted │ │ │ │◄─ 9 dialResponse ──────│ │ │ │ │ │◄─ 10 state change │ │ │ │◄─ 11 callStateChanged (UNSOL) │ │ │ │ 12 pollCalls → getCurrentCalls() ─►│ │ │ │ │◄─ 13 call list [DIALING] ──────────│ │ │ │ │ 14 handlePollCalls(): update connection │ │ ③ STATUS TO THE UI (upstream) │ │ │ │ │◄─ 15 TelephonyConnection.setDialing() (adapter Binder) │ │ │ 16 Call state = DIALING │ │◄─ 17 InCallService.onCallAdded / onStateChanged │ │ 18 UI shows "Dialing" │ │ │ │ ④ PROGRESS: same poll + propagate cycle for every transition │ │ │ ◄════ ALERTING (network sent CC ALERTING; ringback) ══════════════════════════════════│ │ ◄════ ACTIVE (network sent CC CONNECT; UE sends CONNECT ACK) ══════════════════════│ │ UI shows timer / "Active"
- Request down
placeCall→ Telecom →createConnection→Phone.dial()→RIL.dial()→ HAL → modem. Each hop is a Binder or HAL call; only the first few are synchronous in the caller's thread. - Solicited response The HAL returns
dialResponsematching the request serial. This only means "the modem accepted the request", not that the call connected. - Unsolicited indications Every call-state change arrives as
IRadioVoiceIndication.callStateChanged()(legacy nameRIL_UNSOL_RESPONSE_CALL_STATE_CHANGED). It carries no details, so the tracker callsgetCurrentCalls()and diffs the returnedDriverCalllist against its connections inhandlePollCalls(). - Propagate up
GsmCdmaConnectionchange →TelephonyConnection.setDialing()/setActive()→ TelecomCallstate →InCallServicecallbacks → UI.
How state names map across layers
Modem / RIL (DriverCall.State) | Telephony (Call.State) | Telecom Connection | UI / android.telecom.Call | AT +CLCC stat |
|---|---|---|---|---|
| DIALING | DIALING | STATE_DIALING | STATE_DIALING | 2 |
| ALERTING | ALERTING | STATE_DIALING (Telecom has no separate alerting state) | STATE_DIALING | 3 |
| ACTIVE | ACTIVE | STATE_ACTIVE | STATE_ACTIVE | 0 |
| HOLDING | HOLDING | STATE_HOLDING | STATE_HOLDING | 1 |
| INCOMING | INCOMING | STATE_RINGING | STATE_RINGING | 4 |
| WAITING | WAITING | STATE_RINGING (call waiting) | STATE_RINGING | 5 |
| (absent from list) | DISCONNECTED | STATE_DISCONNECTED + DisconnectCause | STATE_DISCONNECTED | - |
At a coarser level, PhoneConstants.State (IDLE / RINGING / OFFHOOK) is what TelephonyManager.getCallState() exposes as CALL_STATE_IDLE / CALL_STATE_RINGING / CALL_STATE_OFFHOOK.
Phone object. Only the middle changes: instead of RIL poll cycles, ImsPhoneCallTracker gets explicit callbacks (onCallProgressing, onCallStarted, onCallTerminated) from the vendor session through ImsCall.Listener. No polling is needed on IMS.GsmCdmaCallTracker fetches the full call list and reconciles it. Also expect "what is the difference between dialResponse success and the call being connected?" (acceptance vs CC CONNECT).Telecom API vs Telecomm vs Telephony
Three different things in AOSP are all loosely called "telecom". Mixing them up is a classic interview stumble.
Think of an airline system. The published rulebook (what a ticket, gate and boarding pass are) is the API. The airport control tower that routes every flight regardless of airline is the service. Each airline's operations desk that actually flies its planes is the implementation. In Android the rulebook is android.telecom in frameworks/base/telecomm, the control tower is packages/services/Telecomm (CallsManager), and the airline desk is com.android.services.telephony inside packages/services/Telephony, which flies cellular calls.
| Layer | Source project | Package | What it is |
|---|---|---|---|
| API | frameworks/base/telecomm/ | android.telecom | The public Telecom framework API: TelecomManager, Connection, ConnectionService, InCallService, PhoneAccount, Call. Just the contracts that apps and the stack code against. |
| Service | packages/services/Telecomm (double m) | com.android.server.telecom | The call router and state machine running in system_server: TelecomServiceImpl, CallsManager, Call, ConnectionServiceWrapper, InCallController, CallAudioManager, PhoneAccountRegistrar. |
| Implementation | packages/services/Telephony | com.android.services.telephony | Telephony's implementation of the ConnectionService API: the glue that lets the generic Telecom router drive cellular (CS and IMS) calls. |
| Telephony framework | frameworks/opt/telephony | com.android.internal.telephony (+ .imsphone) | Phone, GsmCdmaPhone, ImsPhone, call trackers, RIL, ServiceStateTracker. Loaded into com.android.phone. |
packages/services/Telephony; it lives in packages/services/Telecomm and runs in system_server. What lives in Telephony is the telephony-side ConnectionService that Telecom binds to.What packages/services/Telephony actually is
It builds the Phone app, com.android.phone: a persistent, headless system app (not the Dialer). It carries two concerns.
The Telecom bridge
com.android.services.telephony: Telephony's ConnectionService and Connection implementations, bound by Telecom with BIND_TELECOM_CONNECTION_SERVICE.
Everything else telephony
PhoneInterfaceManager (the ITelephony Binder implementation behind TelephonyManager), PhoneGlobals, CarrierConfigLoader, mobile network settings UI, emergency dialer, notifications.
Classes in com.android.services.telephony
| Class | Role |
|---|---|
TelephonyConnectionService | Extends android.telecom.ConnectionService. Entry point Telecom binds to. onCreateOutgoingConnection() / onCreateIncomingConnection() pick the Phone for the SIM slot, handle emergency calls and domain selection, then call Phone.dial() or attach to the ringing call. |
TelephonyConnection (abstract) | Extends android.telecom.Connection. Wraps the internal com.android.internal.telephony.Connection (the originalConnection: a GsmCdmaConnection or ImsPhoneConnection). Maps radio state to Telecom state (setDialing(), setActive(), setDisconnected()). During silent redial the originalConnection is swapped while the TelephonyConnection (and the UI call) survives. |
GsmConnection, CdmaConnection | Concrete TelephonyConnection subclasses (GSM/UMTS/LTE and IMS calls use GsmConnection). |
PstnIncomingCallNotifier | Registers for new ringing connections on each Phone and tells Telecom via TelecomManager.addNewIncomingCall(). The start of the MT flow. |
ImsConference, TelephonyConferenceController, ImsConferenceController, CdmaConferenceController | Model conference calls on top of Telecom. |
RadioOnHelper / RadioOnStateListener (older: EmergencyCallHelper, EmergencyCallStateListener), EmergencyTonePlayer | Emergency orchestration: turn the radio on (exit airplane mode), wait for service, then place the call. |
DisconnectCauseUtil | Maps internal android.telephony.DisconnectCause / precise causes to Telecom DisconnectCause (label and tone shown in the UI). |
TelephonyConnectionServiceProxy | Abstraction and test seam over the connection service. |
Dialer ─placeCall()─► Telecomm (com.android.server.telecom : CallsManager) [system_server]
│ binds ConnectionService (BIND_TELECOM_CONNECTION_SERVICE)
▼
packages/services/Telephony : com.android.services.telephony [com.android.phone]
TelephonyConnectionService.onCreateOutgoingConnection()
├─ creates TelephonyConnection (wraps GsmCdmaConnection / ImsPhoneConnection)
└─ GsmCdmaPhone.dial() ──► CallTracker ──► RIL / ImsService ──► modem / SIP
... state returns ...
TelephonyConnection.setDialing()/setActive() ──► Telecomm ──► InCallService (UI)
packages/services/Telephony is the com.android.phone app; its com.android.services.telephony package is Telephony's ConnectionService, the adapter that plugs Phone → CallTracker → RIL → modem into the generic Telecom router, which lives in packages/services/Telecomm in system_server."CS mobile-originated call flow
From GsmCdmaCallTracker downwards, the CS call is a RIL request followed by standard 3GPP mobility management (MM) and call control (CC) signalling performed entirely by the modem.
An old telephone exchange: you lift the handset and ask the operator for a line (service request), the operator checks who you are (authentication), you give the number (SETUP), the operator says "connecting you" (CALL PROCEEDING), you hear it ringing at the other end (ALERTING), and the other person picks up (CONNECT). In the real system the operator is the MSC, the conversation with it is 3GPP TS 24.008 CC signalling run by the modem, and Android only sees the result as DriverCall states through the RIL.
AOSP classes (framework to RIL)
| Order | Class | Package | Role in a CS dial |
|---|---|---|---|
| 1 | GsmCdmaCallTracker | com.android.internal.telephony | CS call state machine; owns mForegroundCall, mBackgroundCall, mRingingCall and the mConnections[] array. |
| 2 | GsmCdmaConnection | same | One CS call leg; holds number, state, timestamps and the disconnect cause. |
| 3 | GsmCdmaCall | same | Call container (foreground / background / ringing) grouping connections, used for hold and multiparty. |
| 4 | CommandsInterface | interface | Abstraction the trackers talk to; RIL implements it (a test double can too). |
| 5 | RIL | same | dial(address, isEmergency, …, clirMode, uusInfo, result) → RILRequest with a serial → IRadioVoice.dial(serial, Dial) (legacy RIL_REQUEST_DIAL). |
| 6 | VoiceResponse / VoiceIndication (older RadioResponse / RadioIndication) | same | Receive the solicited dialResponse and the unsolicited callStateChanged, callRing. |
| 7 | DriverCall, AsyncResult | same | Modem's view of one call from getCurrentCalls(); async completion wrapper for Message callbacks. |
| 8 | CallFailCause, DisconnectCause | internal / android.telephony | Modem / 24.008 cause (from getLastCallFailCause()) mapped to an app-visible reason. |
| 9 | TelephonyConnection | com.android.services.telephony | Maps internal state to Telecom Connection state. |
Framework and modem sequence
[AOSP - identical on MTK and QCOM]
GsmCdmaPhone.dialInternal()
→ GsmCdmaCallTracker.dial(number, …)
new GsmCdmaConnection(DIALING); mForegroundCall.attach(conn)
→ mCi.dial(...) = RIL.dial(address, clirMode, uusInfo, obtainMessage(EVENT_OPERATION_COMPLETE))
→ IRadioVoice.dial(serial, dialInfo) [AIDL radio HAL]
→ vendor rild
[MTK vendor path] [QCOM vendor path]
MTK RIL (RfxController, RmmCallReq) qcrild (VoiceModule / qcril_qmi_voice)
→ MIPC or AT: ATD<number>; → QMI VOICE: dial_call_req
→ MD1 modem CM/MM/RRC → MPSS CM/MM/RRC
[3GPP over the air - same spec for both vendors]
UE Network (RAN + MSC)
│── RRC Connection Request ────────────►│
│◄─ RRC Connection Setup ───────────────│
│── CM SERVICE REQUEST (MM) ───────────►│
│◄─ AUTHENTICATION REQ / RSP, SECURITY MODE ─►│
│── SETUP (called number, bearer cap) ─►│ CC (TS 24.008)
│◄─ CALL PROCEEDING ────────────────────│
│◄─ RAB / channel assignment ───────────│ traffic channel allocated
│◄─ ALERTING ───────────────────────────│ ringback tone to caller
│◄─ CONNECT ────────────────────────────│ callee answered
│── CONNECT ACKNOWLEDGE ───────────────►│
│═══════════ voice on CS bearer ════════│
[State back up]
modem state change → rild → IRadioVoiceIndication.callStateChanged()
→ GsmCdmaCallTracker.pollCallsWhenSafe() → RIL.getCurrentCalls()
→ handlePollCalls(): DriverCall DIALING → ALERTING → ACTIVE
→ GsmCdmaConnection.update() → Phone.notifyPreciseCallStateChanged()
→ TelephonyConnection.setActive()
→ Telecom → InCallService (in-call screen, timer starts)
Hang-up (CS)
- User taps end InCallUI →
InCallAdapter.disconnectCall()→ Telecom →Connection.onDisconnect()→TelephonyConnection.hangup(). - Tracker
GsmCdmaCallTracker.hangup(conn)→RIL.hangupConnection(index)(orhangupForegroundResumeBackground/hangupWaitingOrBackground). - Air interface UE sends CC
DISCONNECT→ networkRELEASE→ UERELEASE COMPLETE; RRC connection released. - Cleanup
callStateChanged→ poll → call missing from list →getLastCallFailCause()→GsmCdmaConnection.onDisconnect(cause)→setDisconnected(DisconnectCause)→ UI shows "Call ended".
MediaTek CS vendor stack
| Layer | MTK component | Typical log tag |
|---|---|---|
| HAL server | MTK RIL daemon implementing the radio HAL (plus vendor.mediatek.hardware.mtkradioex extensions) | Rfx*, Rmm* |
| Request / URC handler | RmmCallReq (requests), RmmCallUrcHdlr (unsolicited) | RmmCallUrcHdlr |
| IPC to modem | MIPC (MipcVsClient voice service) or AT commands over CCCI | AT> / AT<, MipcVsClient |
| Modem | MD1: MM/CC/RRC | md1, ccci |
// Typical MTK radio log markers (CS MO)
GsmCdmaCallTracker: dial
RILJ : [0001]> DIAL [PHONE0]
AT> ATD+15551234567;
AT< OK
AT< +CLCC: 1,0,2,0,0,"+15551234567",145 // stat 2 = dialing
AT< +CLCC: 1,0,3,0,0,"+15551234567",145 // stat 3 = alerting
AT< +CLCC: 1,0,0,0,0,"+15551234567",145 // stat 0 = active
GsmCdmaCallTracker: onDisconnect cause=NORMAL
Qualcomm CS vendor stack
| Layer | QCOM component | Typical log tag |
|---|---|---|
| HAL server | qcrild implementing the radio HAL (plus vendor.qti.hardware.radio.* extensions) | QCRIL, qcril |
| Request handler | VoiceModule, qcril_qmi_voice | QCRIL_VOICE |
| IPC to modem | QMI VOICE service over the QMI / IPC router (shared memory) | QMI_VOICE |
| Modem | MPSS: CS/PS protocol stacks | QXDM / DIAG logs |
// Typical QCOM radio log markers (CS MO)
GsmCdmaCallTracker: dial
RILJ : [0001]> DIAL [PHONE0]
QCRIL_VOICE: dial_call_req
QMI_VOICE: dial response result=SUCCESS
RILJ : [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED
RILJ : [0002]> GET_CURRENT_CALLS
GsmCdmaCallTracker: update phone state OFFHOOK
RILJ, GsmCdmaCallTracker, ImsPhoneCallTracker, Telecom) are stable.DriverCall states. A strong answer also mentions that the CS MO starts with a CM SERVICE REQUEST (and on LTE without VoLTE, an EXTENDED SERVICE REQUEST for CSFB first).IMS mobile-originated call flow
For an IMS call the framework path is the same down to GsmCdmaPhone.dial(), which delegates to ImsPhone.dial(). From there the call is handed to the vendor's ImsService, which runs the SIP signalling and sets up RTP media. The CS radio HAL (IRadioVoice.dial) is not used for a VoLTE dial.
Placing an IMS call is like booking a conference room through a company's facilities team. You must already have a badge (IMS registration). You send a booking request (SIP INVITE) with your requirements (SDP offer: codecs, ports). Facilities reserves the room and AV kit first (preconditions and the QCI-1 bearer), and only then rings the other person (180 Ringing). In Android, the booking desk you talk to is the vendor MmTelFeature, reached through ImsCall and IImsCallSession; the "room" is the dedicated voice bearer the network sets up.
Prerequisite: IMS must be registered
Registration pieces (before any IMS call)
ImsResolver binds the vendor ImsService; DataNetworkController brings up the IMS PDN (APN ims); the P-CSCF address comes from PCO; the vendor stack sends SIP REGISTER, gets a 401 AKA challenge, re-registers and receives 200 OK; ImsRegistrationImplBase reports "registered" to ImsPhone, which lights the VoLTE indicator.
Bearers for voice
QCI 5 (non-GBR) carries SIP signalling on the IMS PDN default bearer. QCI 1 (GBR) carries RTP voice on a dedicated bearer that the network activates during call setup (P-CSCF → PCRF over Rx → PGW). On 5G SA the equivalents are 5QI 5 and 5QI 1 QoS flows.
Registration, AKA, SIP/SDP details and SRVCC are covered in depth in IMS & VoLTE; this page keeps only what you need for the call flow.
AOSP classes (framework to ImsService)
| Order | Class | Role in an IMS dial |
|---|---|---|
| 1 | ImsPhone | "Shadow" phone owned by GsmCdmaPhone; dial() entry for IMS calls. |
| 2 | ImsPhoneCallTracker | IMS call state machine (the IMS mirror of GsmCdmaCallTracker). |
| 3 | ImsPhoneConnection, ImsPhoneCall | One IMS call leg, and the foreground / background / ringing containers. |
| 4 | ImsCallProfile | Call type (voice / video / emergency), media capabilities, extras. |
| 5 | ImsCall | Framework object for one IMS session: start(), accept(), hold(), terminate(). |
| 6 | ImsCallSession / IImsCallSession | Binder interface to the vendor's session object. |
| 7 | MmTelFeature (vendor subclass) | createCallSession(profile) returns an ImsCallSessionImplBase; vendor code runs SIP and media. |
| 8 | ImsReasonInfo | IMS failure / termination reason (SIP codes and local causes). |
| 9 | TelephonyConnection | Maps ImsPhoneConnection state to Telecom Connection state. |
IMS MO sequence with the SIP ladder
[AOSP - identical on MTK and QCOM]
GsmCdmaPhone.dial() useImsForCall() == true
→ ImsPhone.dial() → dialInternal()
→ ImsPhoneCallTracker.dial()
new ImsPhoneConnection (DIALING); build ImsCallProfile
→ ImsManager.makeCall(profile, callees, listener)
→ MmTelFeature.createCallSession(profile) [Binder to vendor ImsService]
→ ImsCall.start(session, number)
→ IImsCallSession.start(callee, profile)
→ vendor ImsCallSessionImpl
[VENDOR IMS stack - SIP signalling on QCI 5]
UE P-CSCF / S-CSCF / TAS Remote UE
│── INVITE (SDP offer) ─────►│──────────────────────────────►│
│◄─ 100 Trying ──────────────│ │
│◄─ 183 Session Progress (SDP answer, preconditions) ────────│
│ ... P-CSCF typically starts QCI 1 from the SDP answer (around 183, not strictly at PRACK) ...
│── PRACK ──────────────────►│──────────────────────────────►│
│◄─ 200 OK (PRACK) ──────────│◄──────────────────────────────│
│── UPDATE (local preconditions met) ───────────────────────►│
│◄─ 200 OK (UPDATE) ─────────│◄──────────────────────────────│
│◄─ 180 Ringing ─────────────│◄──────────────────────────────│
│◄─ 200 OK (INVITE) ─────────│◄──────────────────────────────│ callee answered
│── ACK ────────────────────►│──────────────────────────────►│
│═══════════ RTP / RTCP voice on QCI 1 bearer ═══════════════│
[Parallel: dedicated bearer - network initiated]
P-CSCF → PCRF (Rx) → PGW → MME → eNB → UE:
ACTIVATE DEDICATED EPS BEARER CONTEXT REQUEST (QCI 1, TFT for RTP ports)
(framework may see it as QosBearerSession in DataCallResponse; it does not request it)
[State back to UI]
vendor → ImsCallSessionListener.callSessionProgressing / callSessionStarted
→ ImsCall.Listener.onCallProgressing / onCallStarted
→ ImsPhoneCallTracker updates ImsPhoneConnection (ALERTING → ACTIVE)
→ TelephonyConnection.setActive() → Telecom → InCallService
IRadioVoice.dial" is wrong: the VoLTE dial is carried by the ImsService (vendor IMS HAL or IPC to the modem's IMS stack), not the CS dial request. Second, "the framework calls setupDataCall for the QCI 1 bearer" is wrong: the IMS PDN is set up by DataNetworkController, but the dedicated voice bearer is activated by the network in response to the P-CSCF's Rx request during call setup. Third, "QCI 1 is set up at PRACK" is too strict: the P-CSCF usually starts Rx/PCC when it has the SDP answer in 183; PRACK only acknowledges that 183 reliably.Hang-up (IMS)
InCallUI end → Telecom → TelephonyConnection.hangup() → ImsPhoneCallTracker.hangup() → ImsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED) → vendor sends SIP BYE (or CANCEL if the call was still ringing and not answered) → 200 OK → the network releases the QCI 1 bearer → onCallTerminated → setDisconnected().
Vendor IMS stacks
| Layer | MediaTek | Qualcomm |
|---|---|---|
| ImsService APK | com.mediatek.ims (ImsService implementation, MtkMmTelFeature-style classes) | org.codeaurora.ims (QTI ImsService; RCS is a separate vendor.qti.imsrcs component) |
| Feature / session | Vendor MmTelFeature + ImsCallSessionImpl | QtiMmTelFeature-style feature + ImsCallSessionImpl |
| Path to modem | MTK IMS RIL extensions (RmmIms*, RmmImsUrcHdlr), MIPC / AT to MD | IMS radio HAL (vendor.qti.hardware.radio.ims) → qcrild IMS module → QMI IMSA / IMS services |
| SIP stack location | Device dependent: older platforms used AP-side vendor IMS daemons; newer platforms run IMS signalling in the modem | IMS stack runs on the modem (MPSS); the APK coordinates and exposes AOSP APIs |
| Media | RTP handled in modem / audio DSP; ImsMedia on newer builds | RTP and vocoder in modem / ADSP; call audio routed by the audio HAL |
| Logs | ImsService, RmmImsUrcHdlr, IMS_RILA; SIP in modem logs (ELT) | ImsCallSessionImpl, QCRIL_IMS; SIP in QXDM / QCAT |
// Typical IMS log markers (both vendors, AOSP part)
ImsPhone : dial: IMS registered, dialing over IMS
ImsPhoneCallTracker: dial clirMode=0
ImsPhoneCallTracker: onCallInitiating
ImsPhoneCallTracker: onCallProgressing
ImsPhoneCallTracker: onCallStarted
ImsPhoneCallTracker: onCallTerminated reasonCode=501 extraMessage=...
// 501 = ImsReasonInfo.CODE_USER_TERMINATED
IRadioVoice, that registration is a precondition, and why preconditions exist (reserve the QCI 1 bearer before alerting so the callee never answers into silence). Be ready to say which side sends 183/PRACK/UPDATE and what 180 vs 183 mean.IMS internals: class significance and dependencies
Above the Phone object IMS looks like CS, but underneath it is a different world: a vendor ImsService hosting an MmTelFeature, reached over Binder, that runs the real SIP session. Knowing who depends on whom is what separates "knows the names" from "understands the wiring".
A franchise restaurant. Head office (ImsResolver) picks which franchisee serves each town (binds the vendor ImsService per SIM slot). The regional manager (ImsManager) is how head office talks to that franchisee. The franchisee's kitchen (MmTelFeature) actually cooks, one order ticket per meal (ImsCallSession). The waiter (ImsPhoneCallTracker) takes orders and relays kitchen updates to the customer (TelephonyConnection → Telecom → UI). Head office sets the menu rules (ImsConfig / provisioning). Swap the franchisee (MTK vs QCOM) and the customer-facing experience stays the same because every kitchen implements the same AOSP interface.
IMS MO internal lanes
GsmCdmaPhone ImsPhone ImsPhoneCallTracker ImsCall / Session ImsService (vendor)
│ │ │ │ │
① SETUP (downstream)
│─1 delegate ───►│ │ │ │
│ │─2 dial() ────────►│ new ImsPhoneConnection│ │
│ │ │ 3 build ImsCallProfile│ │
│ │ │─4 ImsCall.start() ───►│ │
│ │ │ │─5 createCallSession►│
② VENDOR SIP + MEDIA │
│ │ │ │ 6 INVITE → P-CSCF │
│ │ │ │ 7 183/PRACK/UPDATE│
│ │ │ │ + QCI 1 bearer │
│ │ │ │ 8 180 → 200 → ACK │
③ STATE UP (upstream)
│ │ │ │◄─9 SessionListener ─│
│ │ │◄─10 ImsCall.Listener ─│ │
│ │◄─11 conn update ──│ │ │
12 TelephonyConnection.setActive() → Telecom → InCallService
A. Discovery, binding and registration
| Class | Significance | Depends on / talks to |
|---|---|---|
ImsResolver | Runs in the phone process; discovers and binds the right vendor ImsService per slot and per feature (MMTEL, RCS, emergency MMTEL); rebinds on carrier change. | PackageManager, CarrierConfig (carrier-specific ImsService override), the device default from overlay config. |
ImsManager (com.android.ims) | Per-slot internal facade to IMS: VoLTE / WFC enablement, makeCall(), takeCall(), holds the MmTelFeatureConnection. | Uses ImsResolver's binding; talks to MmTelFeature over Binder; used by ImsPhone and ImsPhoneCallTracker. |
ImsService (vendor) | Vendor APK entry point extending android.telephony.ims.ImsService; creates feature objects per slot. | Bound by ImsResolver; provides MmTelFeature, RcsFeature, ImsRegistrationImplBase, ImsConfigImplBase. |
MmTelFeature | The MMTEL feature: createCallSession(), capability reporting, SMS over IMS, notifyIncomingCall(). The real call factory. | Implemented by the vendor; creates ImsCallSessionImplBase objects. |
ImsRegistrationImplBase | Reports SIP registration state (registered / registering / deregistered, plus transport LTE / NR / IWLAN). | Vendor → framework; surfaced to ImsPhone and via ImsMmTelManager callbacks. |
ImsMmTelManager, ProvisioningManager | Public APIs (android.telephony.ims) for Settings and carrier apps: VoLTE / VT / WFC toggles, provisioning keys, registration and capability callbacks. | Backed by the phone process (ImsManager, ImsConfig). |
ImsConfig / ImsConfigImplBase | Carrier provisioning items (for example VoLTE and VT provisioning status, AMR mode sets, SIP timers). Decides whether IMS calling is even allowed. | CarrierConfig + vendor; read and written through ProvisioningManager. |
B. Phone and call tracking
| Class | Significance | Depends on / talks to |
|---|---|---|
ImsPhone | IMS shadow phone, a child of GsmCdmaPhone (mImsPhone). Entry dial() for IMS, holds IMS service state, supplementary services over Ut, and the silent-redial hooks. | Uses ImsManager; owns ImsPhoneCallTracker; mDefaultPhone points back to GsmCdmaPhone. |
ImsPhoneCallTracker | IMS call state machine: MO/MT, hold / swap / merge, onCallStartFailed (CS retry trigger), onCallTerminated, reason-to-cause mapping. | Uses ImsManager; creates ImsCall and ImsPhoneConnection; registers the MmTelFeature.Listener for incoming calls. |
ImsPhoneConnection | One IMS call leg (the internal Connection); wraps an ImsCall; is the originalConnection behind TelephonyConnection. | Owned by ImsPhoneCallTracker. |
ImsPhoneCall | Foreground / background / ringing container, like GsmCdmaCall. | Holds ImsPhoneConnections. |
C. Call session and media
| Class | Significance | Depends on / talks to |
|---|---|---|
ImsCall | One IMS call session in the framework; dispatches ImsCall.Listener events (started, held, merged, terminated). | Runs over ImsCallSession; used by the tracker and connection. |
ImsCallProfile | Call attributes: service type (normal / emergency), call type (voice / video), restrict cause, extras, and the media profile. | Built by ImsPhoneCallTracker; consumed by createCallSession(). |
ImsStreamMediaProfile | Media direction (send / receive / inactive) and quality inside the profile; drives hold / resume SDP. | Part of ImsCallProfile. |
IImsCallSession / ImsCallSession | The AIDL bridge from framework ImsCall to the vendor's SIP session. | Implemented by vendor ImsCallSessionImplBase subclass. |
ImsCallSessionListener | Vendor → framework callbacks: initiating, progressing, started, updated, start failed, terminated. | Called by the vendor; received by ImsCall. |
D. Supplementary services, emergency and errors
| Class | Significance | Depends on / talks to |
|---|---|---|
ImsUtInterface / ImsUt | Supplementary service settings over Ut/XCAP: call forwarding, barring, waiting, CLIR. Persistent settings, not in-call SIP. | Vendor Ut/XCAP client; driven by ImsPhone for MMI codes and Settings. |
ImsEcbm | IMS Emergency Callback Mode. | Vendor ImsService; used by the emergency flow. |
ImsExternalCallTracker | Tracks calls on other devices sharing the same number (multi-endpoint) so they can be shown or pulled. | Fed by vendor dialog-event info; surfaced through ImsPhone. |
ImsReasonInfo | Why a call failed or ended: maps SIP responses and local causes, for example CODE_LOCAL_CALL_CS_RETRY_REQUIRED that drives CS fallback. | Produced by vendor; consumed in onCallStartFailed / onCallTerminated; mapped to DisconnectCause. |
Dependency graph
PUBLIC API (android.telephony.ims) FRAMEWORK (com.android.internal.telephony.imsphone)
ImsMmTelManager / ProvisioningManager GsmCdmaPhone ──delegates IMS dial──► ImsPhone
│ settings, callbacks │ owns
▼ ▼
TELEPHONY (com.android.ims) ImsPhoneCallTracker
ImsManager ──uses──► ImsResolver ──binds──► vendor ImsService │ owns │ creates
│ (MmTelFeature) ▼ ▼
└──────── MmTelFeatureConnection ───────────┘ ImsPhoneConnection ImsCall
│ createCallSession() │ wraps │ over
▼ └─────────────►│
IImsCallSession (AIDL) ◄───────────────────────────── ImsCall
│
vendor ImsCallSessionImpl ──► SIP INVITE ... + RTP media
│ ImsReasonInfo on failure ──► CS fallback
▼
ImsCallSessionListener ─► ImsCall.Listener ─► ImsPhoneCallTracker ─► TelephonyConnection ─► Telecom ─► UI
ImsResolver binds the vendor ImsService; ImsManager is the framework's handle to its MmTelFeature; ImsPhone (child of GsmCdmaPhone) owns ImsPhoneCallTracker, which builds an ImsCallProfile and starts an ImsCall over an IImsCallSession created by MmTelFeature; the vendor runs SIP/RTP and reports back via ImsCallSessionListener → ImsCall.Listener → ImsPhoneConnection → TelephonyConnection → Telecom → UI. Failures come back as ImsReasonInfo, which can trigger CS fallback.ImsResolver sees the binder death, features go unavailable, ImsPhone goes out of IMS service, new calls go CS until rebinding and re-registration).Incoming (MT) call flow
The incoming path runs the other way: the network pages the device, the modem or ImsService reports a new call, and the state flows up to the UI. If the UE was ECM-IDLE, paging first triggers RRC setup and a NAS Service Request so the INVITE or CS SETUP can be delivered. The only later downstream hops are Telecom binding the ConnectionService to create the incoming connection, and the user pressing answer.
A courier arrives at an apartment building. The building intercom buzzes your flat (paging), the doorman checks the parcel and logs it (call tracker creates an INCOMING connection), then calls up to reception (Telecom) which decides whether to ring you, send it to voicemail, or block it (call screening, Do Not Disturb). Your phone rings (InCallService shows the incoming screen). When you say "send it up" (answer), the doorman releases the parcel (acceptCall or SIP 200 OK). In Android the doorman is GsmCdmaCallTracker / ImsPhoneCallTracker, the message to reception is PstnIncomingCallNotifier → addNewIncomingCall(), and reception is CallsManager.
CS MT
Network: PAGING (IMSI/TMSI) on paging channel UE: RRC setup → PAGING RESPONSE Network → UE: SETUP (calling number) UE → Network: CALL CONFIRMED modem → rild → callStateChanged (+ callRing) GsmCdmaCallTracker.pollCalls → handlePollCalls new GsmCdmaConnection (INCOMING) GsmCdmaPhone.notifyNewRingingConnection() PstnIncomingCallNotifier → TelecomManager.addNewIncomingCall() CallsManager.processIncomingCallIntent() → createConnection(isIncoming) → TCS.onCreateIncomingConnection() → setRinging() → InCallService (ring UI) UE → Network: ALERTING (while ringing) User answers → Connection.onAnswer() → GsmCdmaCallTracker.acceptCall() → RIL.acceptCall() → IRadioVoice.acceptCall UE → Network: CONNECT Network → UE: CONNECT ACKNOWLEDGE → ACTIVE
IMS MT
Network: page UE (if idle) → RRC connected
P-CSCF → UE: SIP INVITE (SDP offer)
UE → P-CSCF: 100 Trying, 183 (SDP answer)
QCI 1 typically starts from that SDP answer; PRACK / UPDATE follow
vendor MmTelFeature.notifyIncomingCall(session)
→ ImsPhoneCallTracker (MmTelFeature.Listener)
onIncomingCall → ImsManager.takeCall()
new ImsCall + ImsPhoneConnection (INCOMING)
ImsPhone.notifyNewRingingConnection()
PstnIncomingCallNotifier
→ TelecomManager.addNewIncomingCall()
CallsManager → TCS.onCreateIncomingConnection()
→ setRinging() → InCallService (ring UI)
UE → caller: 180 Ringing
User answers → Connection.onAnswer()
→ ImsPhoneCallTracker.acceptCall()
→ ImsCall.accept() → session.accept()
UE → caller: 200 OK (INVITE)
caller → UE: ACK → RTP flows → ACTIVEMT swimlane across processes
Dialer system_server com.android.phone rild / ImsService Modem / network │ ① NETWORK TO DEVICE (upstream) │ │ │ │ │ │ │◄─ 1 paging / INVITE │ │ │ │◄─ 2 callStateChanged / notifyIncomingCall ─────────│ │ │ │ 3 tracker creates INCOMING connection │ │ │ │ 4 Phone.notifyNewRingingConnection() │ │ │◄─ 5 addNewIncomingCall() (PstnIncomingCallNotifier) │ │ ② TELECOM BUILDS THE CALL (downstream) │ │ │ │ │ 6 filters: blocking, CallScreeningService, DND │ │ │── 7 createConnection(incoming) ─►│ onCreateIncomingConnection │ │ │◄─ 8 setRinging() ────────────────│ │ │ │◄─ 9 onCallAdded │ Ringer plays ringtone, vibration │ │ ③ USER ANSWERS (downstream) │ │ │ │── 10 answerCall()►│── 11 onAnswer() ──►│ 12 acceptCall() │ │ │ │ │── 13 IRadioVoice.acceptCall / session.accept() ───►│ │ │ │ │── 14 CONNECT / 200 OK► │ ④ CONNECTED (upstream) │ │ │ │◄═══ 15 state ACTIVE → setActive() → onStateChanged → "In call" ═════════════════════════════│
Things specific to MT calls
- Call waiting An MT call while another is active appears as
WAITING(CS) or a secondImsPhoneConnectioninmRingingCall; Telecom shows it as a second ringing call. Answering holds the active call (CSswitchWaitingOrHoldingAndActive; IMS hold re-INVITE then accept). - Call screening and blocking Telecom runs
IncomingCallFilterGraph(blocked numbers,CallScreeningService, DND) before ringing. A blocked call is rejected without ever reaching the UI. - Reject CS:
RIL.rejectCall()/hangupWaitingOrBackground→ user busy cause. IMS:ImsCall.reject(CODE_USER_DECLINE)→ SIP486 Busy Hereor603 Declinedepending on carrier. - Missed MT If the caller cancels while ringing: CS disconnect, or IMS
CANCEL→ UE replies487 Request Terminated; Telecom logs a missed call andMissedCallNotifierposts a notification.
Vendor markers for MT: MTK: RmmCallUrcHdlr (CS incoming, +CRING / ECPI URCs) or RmmImsUrcHdlr (IMS incoming). QCOM: QMI_VOICE all-call-status indication (CS) or the QMI IMS incoming indication via QCRIL_IMS (IMS).
addNewIncomingCall); Telecom then creates the Call and asks the ConnectionService to create the incoming Connection. Also expect "caller hears ringing but the callee never rings" and "incoming calls go straight to voicemail" scenarios.CS fallback end to end, with AOSP code
"CS fallback" means two different things. Separate them clearly in an interview.
You try to pay by card at a shop. Either the shop has no card reader at all, so you walk to the cash counter before even trying (radio CSFB: an LTE network without VoLTE moves you to 2G/3G before the call). Or the card terminal declines your card with "please pay cash", so the cashier quietly takes cash instead without cancelling your order (framework IMS → CS fallback: the IMS attempt fails with a CS-retry reason and the framework silently redials on CS). In Android the first is a modem/NAS procedure invisible to the framework; the second is Phone.CS_FALLBACK and initiateSilentRedial() in ImsPhone / GsmCdmaPhone.
1. Radio / NAS CSFB (3GPP)
A UE registered on LTE via combined EPS/IMSI attach but without VoLTE. For a voice call it sends NAS EXTENDED SERVICE REQUEST; the MME (via SGs to the MSC) and eNB move it to 2G/3G by RRC release with redirection, PS handover or cell change order. The CS call then proceeds normally. For MT, the MSC pages over SGs and LTE; the UE answers with the same ESR. Handled entirely by the modem and network.
2. Framework IMS → CS fallback (AOSP)
The Android framework tries the call over IMS; if IMS cannot take it, it redials on the CS domain. Two flavours: synchronous (dial-time), where ImsPhone throws CallStateException(CS_FALLBACK), and asynchronous (runtime), where the IMS call starts but fails with CODE_LOCAL_CALL_CS_RETRY_REQUIRED and a silent redial happens.
Radio CSFB ladder (MO)
UE (on LTE, no VoLTE) eNB MME MSC (via SGs) │── EXTENDED SERVICE REQUEST (CSFB MO) ──►│ │ │ │◄─ UE context modify (CSFB indicator) │◄─ RRC Release with redirection to GERAN/UTRAN (or PS HO / CCO) │ ... UE tunes to 2G/3G cell, reads system info ... │── CM SERVICE REQUEST ─────────────────────────────────►│ │── SETUP ──────────────────────────────────────────────►│ │◄─ CALL PROCEEDING / ALERTING / CONNECT ────────────────│ │ call ends → UE returns to LTE (fast return / reselection)
Typical CSFB latency cost is one to a few extra seconds of call setup. Framework only sees a normal CS dial via GsmCdmaCallTracker; the RAT change shows up in ServiceStateTracker.
The sentinel constant in Phone.java
// frameworks/opt/telephony/.../com/android/internal/telephony/Phone.java
public static final String CS_FALLBACK = "cs_fallback";
public static final String CS_FALLBACK_SS = "cs_fallback_ss"; // supplementary-service (Ut) fallback
It is just a string carried in a CallStateException message. When the IMS layer throws it, the CS layer recognises it and retries.
Step 1: decide whether to try IMS at all, useImsForCall()
// GsmCdmaPhone.java
private boolean useImsForCall(DialArgs dialArgs) {
return isImsUseEnabled()
&& mImsPhone != null
&& (mImsPhone.isVoiceOverCellularImsEnabled() || mImsPhone.isWifiCallingEnabled()
|| (mImsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
&& (mImsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
}
If VoLTE/VoWiFi is not enabled or IMS is out of service, IMS is never attempted and the call goes straight to CS. That is a direct CS dial, not a fallback. A related trap: on LTE-only without IMS, ServiceState.getState() (voice) can be out of service while data is in service, so useImsForCall() is false and the CS dial then fails because there is no CS domain.
Step 2: synchronous fallback, try IMS then catch CS_FALLBACK
// GsmCdmaPhone.dial(...)
if ((useImsForCall && (!isMmiCode || isPotentialUssdCode))
|| (isMmiCode && useImsForUt)
|| useImsForEmergency) {
try {
if (DBG) logd("Trying IMS PS call");
chosenPhoneConsumer.accept(imsPhone);
return imsPhone.dial(dialString, dialArgs); // attempt over IMS
} catch (CallStateException e) {
// Do not throw; fall back to circuit switch for emergency calls and MMI codes.
if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
logi("IMS call failed with Exception: " + e.getMessage() + ". Falling back to CS.");
} else {
CallStateException ce = new CallStateException(e.getError(), e.getMessage());
ce.setStackTrace(e.getStackTrace());
throw ce; // real failure: surface it
}
}
}
// ... service / airplane-mode / out-of-service guards ...
if (DBG) logd("Trying (non-IMS) CS call");
chosenPhoneConsumer.accept(this);
return dialInternal(dialString, dialArgs); // the CS dial
Step 3: where ImsPhone throws it (USSD / MMI example)
// ImsPhone.dialInternal(...)
} else if (!mmi.isSupportedOverImsPhone()) {
// MMI not supported by the IMS service: dial with the default (CS) phone
logi("dialInternal: USSD not supported by IMS; fallback to CS.");
throw new CallStateException(CS_FALLBACK);
} else {
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
try {
mmi.processCode();
} catch (CallStateException cse) {
if (CS_FALLBACK.equals(cse.getMessage())) {
logi("dialInternal: fallback to GSM required.");
mPendingMMIs.remove(mmi); // let CS own it
throw cse; // caught by GsmCdmaPhone.dial above
}
}
}
Step 4: asynchronous fallback after the IMS call started
Sometimes IMS accepts the dial, then the modem/IMS stack rejects it with a reason meaning "this must go on CS": ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED.
// ImsPhoneCallTracker: reason-to-cause map
PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
CallFailCause.LOCAL_CALL_CS_RETRY_REQUIRED);
// ImsPhoneCallTracker.mImsCallListener.onCallStartFailed(...)
if (mPendingMO != null) {
if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
&& mRingingCall.getState() == ImsPhoneCall.State.IDLE
&& isForegroundHigherPriority()) {
mForegroundCall.detach(mPendingMO);
removeConnection(mPendingMO);
mImsCallInfoTracker.updateImsCallStatus(mPendingMO);
mPendingMO.finalize();
mPendingMO = null;
// for CSFB, first hang up any lower-priority background call
if (mBackgroundCall.getState().isAlive()) {
try {
hangup(mBackgroundCall);
mPendingSilentRedialInfo = new Pair<>(reasonInfo.getExtraCode() ==
ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY, eccCategory);
} catch (CallStateException ex) { mPendingSilentRedialInfo = null; }
} else {
updatePhoneState();
mPhone.initiateSilentRedial(reasonInfo.getExtraCode() ==
ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY, eccCategory);
}
return;
} else {
sendCallStartFailedDisconnect(imsCall, reasonInfo);
}
}
Step 5: initiateSilentRedial() hands the number back for a CS dial
// ImsPhone.initiateSilentRedial(...)
int cause = CallFailCause.LOCAL_CALL_CS_RETRY_REQUIRED;
AsyncResult ar = new AsyncResult(null,
new SilentRedialParam(mLastDialString, cause, dialArgs), null);
// posted to the main thread to avoid racing with dial() that is still finishing
mContext.getMainExecutor().execute(() -> {
logd("initiateSilentRedial: notifying registrants");
mSilentRedialRegistrants.notifyRegistrants(ar); // default phone redials on CS
});
The listener (in the phone process, wired up so TelephonyConnection can follow the redial) dials again on CS and swaps the originalConnection under the existing TelephonyConnection. The user never sees a failed call, only a slightly longer setup.
The reverse: CS → IMS "VoLTE silent redial"
// GsmCdmaPhone
public void notifyVolteSilentRedial(String dialString, int causeCode) {
AsyncResult ar = new AsyncResult(null,
new SilentRedialParam(dialString, causeCode, mDialArgs), null);
mVolteSilentRedialRegistrants.notifyRegistrants(ar);
}
// ImsPhone.handleMessage(...)
case EVENT_INITIATE_VOLTE_SILENT_REDIAL: {
SilentRedialParam result = (SilentRedialParam) ar.result;
Connection cn = dial(result.dialString,
updateDialArgsForVolteSilentRedial(result.dialArgs, result.causeCode));
if (mDefaultPhone != null) mDefaultPhone.notifyRedialConnectionChanged(cn);
break;
}
Modern path: the Domain Selection Service (Android 14+)
When DomainSelectionResolver.isDomainSelectionSupported() is true, the framework stops hard-coding the redial. onCallStartFailed() records the reason and disconnects; the domain selection service (for example NormalCallDomainSelector / EmergencyCallDomainSelector) decides CS vs PS using 3GPP rules, carrier config and access-network state, and drives the next attempt.
// ImsPhoneCallTracker.onCallStartFailed(...) with domain selection
if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
ImsPhoneConnection conn = findConnection(imsCall);
if (conn != null) {
conn.setImsReasonInfo(reasonInfo);
sendCallStartFailedDisconnect(imsCall, reasonInfo); // service picks next domain
mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(), imsCall.getCallSession(), reasonInfo);
return;
}
}
End-to-end sequence
Telecom / TelephonyConnectionService
│ placeCall()
▼
GsmCdmaPhone.dial()
│ useImsForCall()? ──no──────────────────────────► CS dialInternal() (direct CS, no fallback)
│ yes
▼
imsPhone.dial() ──► ImsPhoneCallTracker ──► ImsService / modem
│ │
│ (A) dial-time: throws CallStateException(CS_FALLBACK)
│ └─ caught in GsmCdmaPhone.dial ──► CS dialInternal() redial on CS
│
└─ (B) runtime: onCallStartFailed(CODE_LOCAL_CALL_CS_RETRY_REQUIRED)
└─ ImsPhone.initiateSilentRedial()
└─ mSilentRedialRegistrants ──► dial on CS silent redial
└─ (or, with domain selection) service picks CS and redials
CS dial on an LTE cell without VoLTE → modem performs radio CSFB (ESR → 2G/3G)
| Trigger | Flavour | Where in code |
|---|---|---|
| VoLTE/VoWiFi disabled or IMS out of service | IMS never tried (direct CS) | useImsForCall() |
| USSD/MMI not supported over IMS | Synchronous | ImsPhone.dialInternal throws CS_FALLBACK |
| Emergency IMS attempt throws | Synchronous | GsmCdmaPhone.dial catch (isEmergency) |
| Modem / IMS requires CS retry during setup | Asynchronous | onCallStartFailed → initiateSilentRedial |
| Any of the above with domain selection enabled | Service-driven | DomainSelectionResolver / domain selector |
| LTE cell, CS dial, no VoLTE | Radio CSFB | Modem NAS (ESR) + network; invisible to AOSP |
logcat -b radio for Falling back to CS (the GsmCdmaPhone.dial catch) and for CODE_LOCAL_CALL_CS_RETRY_REQUIRED / onCallStartFailed / initiateSilentRedial. Correlate with the ImsReasonInfo extra code and the SIP failure in the modem trace (for example 380 Alternative Service, 503, or a local reason) to decide whether the network rejected VoLTE or the device gave up. Some OEM builds add their own CSFB helper classes with extra log lines; those are not AOSP.TelephonyConnection survives and only its originalConnection changes).Emergency call path overview
Emergency calls reuse the same Telecom and Telephony skeleton but with special rules at almost every layer: number detection, radio power, phone selection, domain selection, network procedures and a post-call callback mode.
An ambulance uses the same roads as everyone else, but it can run red lights, switch lanes freely and does not need a toll pass. An emergency call uses the same Dialer → Telecom → Telephony path, but it can turn the radio on from airplane mode, use any SIM or no SIM, use any network (limited service), and take priority over other bearers. The "siren" in the real system is the emergency flag carried from TelephonyConnectionService down to IRadioVoice.emergencyDial or an IMS urn:service:sos INVITE.
- Detect
TelephonyManager.isEmergencyNumber()backed byEmergencyNumberTracker, which merges numbers from the SIM, the network (NAS list via theIRadioVoiceIndication.currentEmergencyNumberListindication), the modem, the built-in database and test config. Telecom marks the call as emergency (and bypasses call redirection and some restrictions). - Power the radio If airplane mode or radio off,
TelephonyConnectionServiceusesRadioOnHelper/RadioOnStateListenerto power on and wait for service (or emergency-only service) with a timeout. - Pick a phone On multi-SIM, choose the slot most likely to succeed (in service, then limited service, SIM present, etc.). A SIM is not required: the modem camps in limited service on any network.
- Pick a domain Carrier config and (on Android 14+)
EmergencyCallDomainSelectordecide IMS vs CS, and on dual-access devices LTE/NR vs Wi-Fi. TheEmergencyStateTrackermanages emergency mode in the modem. - CS emergency
RIL.emergencyDial()→IRadioVoice.emergencyDial()(HAL 1.4+) → modem sends CCEMERGENCY SETUP(no called number needed; the network routes to the PSAP). - IMS emergency Emergency PDN/PDU session (emergency APN) + emergency registration (or unauthenticated emergency session where allowed); INVITE to
urn:service:sos; P-CSCF routes to the E-CSCF → PSAP, with location from the LRF/GMLC. High ARP allows pre-emption of other bearers. - Fallback If the IMS attempt fails, the framework retries on CS (
isEmergencyin theGsmCdmaPhone.dialcatch, orEXTRA_CODE_CALL_RETRY_EMERGENCYin silent redial), and may try another slot. - After the call Emergency Callback Mode (ECBM;
ImsEcbmfor IMS): for a carrier-defined window the device stays reachable for a PSAP callback and data may be restricted.
EmergencyNumberTracker sources), and "what is different in the IMS INVITE?" (urn:service:sos, emergency PDN, E-CSCF routing). Details of emergency registration live in IMS & VoLTE.Where MediaTek and Qualcomm diverge
The Telecom and Telephony frameworks are AOSP and identical on both platforms. Divergence starts at the radio HAL server (rild), the IPC to the modem, and the ImsService / IMS stack.
Two cars with the same dashboard and steering wheel but different engines. The driver (the app and framework) uses the same controls; only a mechanic (a platform engineer reading vendor logs) sees the difference under the bonnet. In Android the dashboard is android.telecom + com.android.internal.telephony + the AIDL radio / IMS interfaces; the engines are MTK's rild + MIPC/AT + MD1, and Qualcomm's qcrild + QMI + MPSS.
| Stage | MediaTek | Qualcomm | Same? |
|---|---|---|---|
| Dialer → Telecom → ConnectionService | AOSP | AOSP | Yes |
GsmCdmaPhone / ImsPhone / call trackers | AOSP (OEMs may add hooks) | AOSP (OEMs may add hooks) | Yes |
| RIL Java | RIL.java | RIL.java | Yes |
| Radio HAL interface | AOSP IRadio* + vendor.mediatek.hardware.mtkradioex extensions | AOSP IRadio* + vendor.qti.hardware.radio.* extensions | Interface yes, extensions no |
| Vendor daemon | MTK rild (Rfx*, Rmm*) | qcrild (modules such as VoiceModule) | No |
| Modem IPC (CS) | MIPC or AT (ATD, +CLCC) over CCCI | QMI VOICE over IPC router / shared memory | No |
| Modem IPC (IMS) | MIPC IMS / RmmImsUrcHdlr | QMI IMS services via qcril IMS HAL | No |
| ImsService APK | com.mediatek.ims | org.codeaurora.ims | No |
| IMS / SIP stack location | Device dependent (AP daemons on older platforms, modem on newer) | Modem (MPSS) | No |
| Modem crash marker | md1 exception, CCCI reset | SSR (subsystem restart), ramdump | No |
| Modem log tools | ELT / Catcher (modem logs, .muxz/.curf) | QXDM / QCAT / PCAP via DIAG | No |
| 3GPP over the air | MM / CC / RRC / NAS (same spec) | Same spec | Yes |
RILJ, trackers, ImsReasonInfo) is identical; below rild you switch keyword sets (AT/MIPC/md1 vs QMI/qcril/SSR) and tools (ELT vs QXDM). Always identify the chipset and log family first.Debug cheat sheet
Most call-failure investigations follow the same order: which path (CS or IMS), how far setup got, what cause code was given at that exact timestamp, and whether the modem was healthy.
Tracking a lost parcel: first find out which courier had it (CS or IMS path), then the last scan (last log marker reached: dial, DIAL request, INVITE, 180, 200), then the reason on the exception slip (SIP code, CallFailCause, NAS cause), and only then ask whether the delivery van broke down (modem SSR / md1 exception). Blaming the van first, without checking the slip, is the classic mistake.
Triage order
- First check Chipset and log family; RF environment (RSRP/RSRQ, PLMN, roaming, SIM state); modem health (SSR,
md1exception, RILserviceDied) near the event. - Which path?
Trying IMS PS callvsTrying (non-IMS) CS callinGsmCdmaPhone;ImsPhoneCallTrackervsGsmCdmaCallTrackerlines. - How far did setup get? Telecom created the call?
RILJ > DIALsent? SIP INVITE sent? 100/183/180 received? - Cause at the exact timestamp SIP response /
ImsReasonInfo,CallFailCause/DisconnectCause, NAS / RRC cause. - Correlate Align framework, radio and modem logs by time; build a hypothesis vs evidence table before concluding.
Key log tags and markers
| Where | Tag / marker | What it tells you |
|---|---|---|
| Telecom | Telecom (CallsManager, InCallController, CreateConnectionProcessor) | Call created, account chosen, connection service bound, UI bound, disconnect cause shown. |
| ConnectionService | TelephonyConnectionService, TelephonyConnection | Phone chosen, emergency handling, state mapping to Telecom. |
| CS | GsmCdmaPhone: Trying (non-IMS) CS call, GsmCdmaCallTracker: dial, onDisconnect cause= | CS dial started / ended with a DisconnectCause. |
| RIL | RILJ [serial]> DIAL, < DIAL, [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED, GET_CURRENT_CALLS, LAST_CALL_FAIL_CAUSE | Request and response pairing, unsolicited indications, fail cause from the modem. |
| IMS | ImsPhoneCallTracker: onCallInitiating / onCallProgressing / onCallStarted / onCallStartFailed / onCallTerminated | Normal setup progression or failure with ImsReasonInfo. |
| Fallback | Falling back to CS, CODE_LOCAL_CALL_CS_RETRY_REQUIRED, initiateSilentRedial | IMS → CS fallback happened, and which flavour. |
| Vendor | MTK: AT> ATD, +CLCC, RmmCallUrcHdlr; QCOM: QCRIL_VOICE, QMI_VOICE, QCRIL_IMS | Modem received the dial and what it reported. |
Commands
# Radio buffer: RILJ, trackers, IMS framework
adb logcat -b radio -v threadtime
adb logcat -b main,system,radio -v threadtime | grep -E "Telecom|TelephonyConnection|CallTracker|RILJ"
# Telecom: current calls, per-call event timeline, bound InCallServices, analytics
adb shell dumpsys telecom
# Telephony state as other apps see it: call state, service state, signal
adb shell dumpsys telephony.registry
# Phone process state, carrier config, subscriptions
adb shell dumpsys phone
adb shell dumpsys carrier_config
adb shell dumpsys isub
# IMS helpers (TelephonyShellCommand)
adb shell cmd phone ims get-ims-service
adb shell cmd phone ims enable # or disable, for the default slot
# Place / end a call from the shell for repro
adb shell am start -a android.intent.action.CALL -d tel:5551234
adb shell input keyevent KEYCODE_ENDCALL
Common failures and what they usually mean
| Symptom | Likely layer | Where to look |
|---|---|---|
Call fails instantly, no DIAL in RILJ | Framework / policy | Telecom rejection (no account, not default dialer, emergency-only), FDN / call barring, out of service, CallStateException in GsmCdmaPhone.dial. |
| VoLTE icon shown but call goes CS | IMS registration / config | useImsForCall() inputs, IMS service state, provisioning, CarrierConfig; possibly a stale icon. |
| IMS call fails with SIP 403 / 404 / 480 / 486 / 488 / 503 / 504 | IMS network / operator | Forbidden / not found / unavailable / busy / SDP mismatch / congestion / timeout. Map via ImsReasonInfo. |
| CS call fails with cause 1 / 17 / 18 / 21 / 34 / 38 / 41 / 42 | CS network | Unassigned number / user busy / no user responding / call rejected / no circuit / network out of order / temporary failure / congestion. |
| Caller hears ringing, callee never rings | Terminating side | Callee registration, paging success, terminating preconditions, diversion or barring at the TAS. |
| Call connects but no audio / one-way audio | Media | SDP addresses/ports, QCI 1 bearer in both directions, codec agreement, RTP counters, audio routing (CallAudioRouteStateMachine, audio HAL mode). |
| Call drops mid-call | RF / mobility / modem | RLF / RRC release, SRVCC failure, SIP BYE with Reason, modem SSR / serviceDied (RADIO_NOT_AVAILABLE). |
| MT calls go straight to voicemail | Reachability | Not registered / out of coverage, paging failure, call forwarding (CFNRc) active, DND or call screening rejecting. |
| Long setup delay | Fallback / CSFB | Silent redial, radio CSFB, EPS fallback from 5G SA, slow preconditions. |
Useful cause-code references
SIP responses (IMS)
380 alternative service (often "use CS / emergency"), 403 forbidden, 404 not found, 408 timeout, 480 temporarily unavailable, 486 busy here, 487 request terminated (cancelled), 488 not acceptable here (codec/SDP), 500 server error, 503 service unavailable, 504 server timeout, 603 decline.
CS CallFailCause (24.008)
1 unassigned number, 16 normal clearing, 17 user busy, 18 no user responding, 19 no answer (user alerted), 21 call rejected, 31 normal unspecified, 34 no circuit available, 38 network out of order, 41 temporary failure, 42 switching equipment congestion; Android adds CALL_BARRED (240), FDN_BLOCKED (241).
RIL errors
RADIO_NOT_AVAILABLE (HAL or modem down; all pending requests fail after serviceDied), REQUEST_NOT_SUPPORTED, GENERIC_FAILURE, OEM_ERROR_* (vendor specific).
dumpsys telecom (per-call event log) and logcat -b radio immediately scores points.Quick revision
- CS voice uses a dedicated circuit in the 2G/3G CS domain; IMS voice is a SIP session with RTP media over an IP bearer.
- VoLTE, VoNR and VoWiFi are all IMS; only the access network differs (LTE, NR SA, Wi-Fi via ePDG).
- Shared top path: Dialer →
TelecomManager.placeCall()→TelecomServiceImpl→CallsManager→ConnectionServiceWrapper→TelephonyConnectionService→Phone.dial(). - Three processes: Dialer app, system_server (Telecom),
com.android.phone(Telephony); plus the vendor rild and ImsService processes. - Telecom → phone process is
IConnectionService, notITelephony;PhoneInterfaceManageris not on the dial path. - The CS/IMS fork is
GsmCdmaPhone.dial()usinguseImsForCall()(IMS enabled, VoLTE/WFC on, IMS in service). - CS path:
GsmCdmaCallTracker→GsmCdmaConnection→RIL.dial()→IRadioVoice.dial()→ rild → modem. - CS over the air: CM SERVICE REQUEST → SETUP → CALL PROCEEDING → ALERTING → CONNECT → CONNECT ACK; release is DISCONNECT → RELEASE → RELEASE COMPLETE.
callStateChangedcarries no details; the CS tracker pollsgetCurrentCalls()and diffsDriverCalls inhandlePollCalls().dialResponsesuccess means the modem accepted the request, not that the call connected.- IMS path:
ImsPhone→ImsPhoneCallTracker→ImsCall→IImsCallSession→ vendorMmTelFeature→ SIP INVITE. - IMS MO ladder: INVITE → 100 → 183 (SDP answer) → PRACK → UPDATE → 180 → 200 OK → ACK → RTP.
- SIP rides QCI 5 / 5QI 5; voice RTP rides a network-initiated GBR QCI 1 / 5QI 1 bearer, typically started from the SDP answer around 183, not strictly at PRACK.
- Preconditions reserve the voice bearer before alerting so the callee never answers into silence.
- The VoLTE dial does not use
IRadioVoice.dial; it goes through the ImsService and vendor IMS path. ImsResolverbinds the vendor ImsService;ImsManageris the framework handle;ImsRegistrationImplBasereports registration.- State returns via
TelephonyConnection.setDialing()/setActive()→ Telecom →InCallService.onCallAdded/onStateChanged. - Telecom has no alerting state; ALERTING still shows as
STATE_DIALING. - MT: tracker creates INCOMING connection →
notifyNewRingingConnection()→PstnIncomingCallNotifier→addNewIncomingCall()→ Telecom →onCreateIncomingConnection()→ ring UI. - Answer: CS
RIL.acceptCall()→ CC CONNECT; IMSImsCall.accept()→ SIP 200 OK → ACK. - Radio CSFB (ESR, redirect to 2G/3G) is invisible to AOSP; framework IMS → CS fallback uses
Phone.CS_FALLBACKand silent redial. - Synchronous fallback:
ImsPhonethrowsCallStateException(CS_FALLBACK), caught inGsmCdmaPhone.dial(). - Asynchronous fallback:
onCallStartFailed(CODE_LOCAL_CALL_CS_RETRY_REQUIRED)→initiateSilentRedial(), posted to the main executor. - Android 14+ domain selection service centralises the CS vs PS choice for normal and emergency calls.
- Voice can be OOS while LTE data is in service if there is no CS domain and IMS is not registered;
useImsForCall()then fails and a CS dial fails too. - Emergency:
EmergencyNumberTracker, radio-on helper, any SIM or no SIM,emergencyDial(CS EMERGENCY SETUP) or IMSurn:service:sos, then ECBM. - MTK vs QCOM diverge only below the HAL: rild + MIPC/AT + MD1 vs qcrild + QMI + MPSS; ImsService
com.mediatek.imsvsorg.codeaurora.ims. - Debug order: path, last successful step, cause at the timestamp, modem health, correlation.
- Tools:
logcat -b radio,dumpsys telecom,dumpsys telephony.registry, QXDM/QCAT or ELT, Wireshark for SIP/RTP. - SIP error + healthy modem means IMS/operator layer;
RADIO_NOT_AVAILABLEafterserviceDiedmeans the HAL or modem went away.
Glossary
- ACK (SIP)
- Final message confirming a 200 OK to an INVITE; after it, media flows.
- ALERTING
- CC message (and call state) meaning the called party is being rung; the caller hears ringback.
- CallFailCause
- Telephony class holding modem / 3GPP 24.008 call failure codes returned by
getLastCallFailCause(). - CallsManager
- The core of Telecom in system_server; creates and tracks every
Call, arbitrates between calls and accounts. - CC (Call Control)
- 3GPP TS 24.008 protocol for CS calls: SETUP, CALL PROCEEDING, ALERTING, CONNECT, DISCONNECT, RELEASE.
- CONNECT
- CC message meaning the call was answered; the receiver replies CONNECT ACKNOWLEDGE.
- ConnectionService
android.telecomservice a calling stack implements so Telecom can create and control its calls; Telephony's isTelephonyConnectionService.- CS (circuit-switched)
- Voice carried on a dedicated channel reserved for the whole call (GSM/UMTS domain).
- CS_FALLBACK
- Sentinel string in
Phone.javathrown in aCallStateExceptionto tellGsmCdmaPhoneto redial on CS. - CSFB
- CS Fallback: 3GPP procedure moving an LTE UE without VoLTE to 2G/3G for a voice call via EXTENDED SERVICE REQUEST.
- Dedicated bearer
- Extra bearer with specific QoS (e.g. QCI 1 GBR for voice), activated by the network for a flow defined by a TFT.
- DisconnectCause
- Reason a call ended;
android.telephony.DisconnectCauseinternally, mapped toandroid.telecom.DisconnectCausefor the UI. - Domain selection
- Android 14+ service deciding whether a call goes over CS or PS (IMS), and over which access.
- DriverCall
- The modem's description of one call (index, state, number) returned by
getCurrentCalls(). - ECBM
- Emergency Callback Mode: a period after an emergency call when the device stays reachable for a PSAP callback.
- EmergencyNumberTracker
- Telephony component merging emergency numbers from SIM, network, modem and database.
- ESR
- EXTENDED SERVICE REQUEST, the NAS message an LTE UE sends to trigger CSFB.
- GsmCdmaCallTracker
- CS call state machine; talks to the RIL and owns CS connections and calls.
- GsmCdmaPhone
- Main
Phoneimplementation per SIM slot; the place where CS vs IMS is decided. - IConnectionService
- Binder interface Telecom uses to talk to a
ConnectionServicein another process. - ImsCall
- Framework object representing one IMS call session over an
IImsCallSession. - ImsCallProfile
- Attributes of an IMS call: service type, call type (voice/video), media profile, extras.
- ImsPhone
- IMS "shadow" phone owned by
GsmCdmaPhone; entry point for IMS calls and silent redial. - ImsPhoneCallTracker
- IMS call state machine; builds profiles, starts
ImsCalls, handles start failures. - ImsReasonInfo
- Code and extra info explaining why an IMS call failed or ended.
- ImsResolver
- Phone-process component that finds and binds the vendor ImsService for each slot and feature.
- ImsService
- Vendor APK implementing AOSP's IMS API; hosts
MmTelFeatureand runs or drives the SIP stack. - InCallService
android.telecomservice bound by Telecom to show and control calls; the Dialer's in-call UI implements it.- IRadioVoice
- AIDL radio HAL interface for voice requests (dial, accept, hangup, getCurrentCalls, emergencyDial).
- MmTelFeature
- ImsService feature for multimedia telephony: creates call sessions, reports capabilities, notifies incoming calls.
- MO / MT
- Mobile-originated (outgoing) and mobile-terminated (incoming) calls.
- Paging
- Network broadcast asking an idle UE to connect because there is an incoming call or data.
- PhoneAccount
- Telecom record describing a calling account (a SIM or a VoIP app) and the ConnectionService that serves it.
- Preconditions
- SIP mechanism (183 with SDP answer / PRACK / UPDATE) that reserves network resources before the callee is alerted. The dedicated voice bearer is usually triggered from the SDP answer, not from PRACK itself.
- PstnIncomingCallNotifier
- Phone-process class that reports new ringing connections to Telecom via
addNewIncomingCall(). - QCI 1 / 5QI 1
- GBR QoS class for conversational voice on LTE / NR; QCI 5 / 5QI 5 is for IMS signalling.
- RIL / RILJ
- Radio Interface Layer;
RIL.java(log tag RILJ) is the framework client of the radio HAL. - rild
- Vendor radio daemon implementing the radio HAL and talking to the modem (MTK rild, Qualcomm qcrild).
- SETUP
- CC message that starts a CS call; carries the called number and bearer capability.
- Silent redial
- Automatic redial on the other domain (IMS to CS or CS to IMS) without the user seeing a failed call.
- SIP INVITE
- SIP request that starts a session; carries the SDP offer for media.
- SRVCC
- Single Radio Voice Call Continuity: hands an active IMS call to CS when leaving VoLTE coverage.
- TelecomServiceImpl
- system_server implementation of
ITelecomService, behindTelecomManager. - TelephonyConnection
- Telecom
Connectionsubclass wrapping an internal telephony connection and mapping its state. - TelephonyConnectionService
- Telephony's
ConnectionServiceincom.android.phone; creates cellular connections and callsPhone.dial(). - useImsForCall()
GsmCdmaPhonecheck deciding whether to attempt the call over IMS.
Interview questions
Fundamentals
What is the difference between a CS call and an IMS call?
A CS call reserves a dedicated circuit in the 2G/3G CS domain for the whole call, set up with 3GPP 24.008 call control (SETUP / ALERTING / CONNECT). An IMS call is a SIP session over an IP bearer; voice is RTP packets on a guaranteed-bit-rate bearer (QCI 1 on LTE, 5QI 1 on NR). On Android, CS goes GsmCdmaCallTracker → RIL → modem; IMS goes ImsPhoneCallTracker → vendor ImsService → SIP.
Walk through the Android stack for an outgoing call at a high level.
Dialer → TelecomManager.placeCall() → TelecomServiceImpl / CallsManager in system_server picks a PhoneAccount → Telecom binds TelephonyConnectionService in com.android.phone → onCreateOutgoingConnection() → GsmCdmaPhone.dial(). If IMS is usable it delegates to ImsPhone → ImsPhoneCallTracker → ImsService/MmTelFeature → SIP INVITE. Otherwise GsmCdmaCallTracker → GsmCdmaConnection → RIL.dial() → IRadioVoice.dial → vendor rild → modem. State flows back through TelephonyConnection → Telecom → InCallService.
What are VoLTE, VoNR and VoWiFi, and what do they have in common?
All three are IMS voice: the same SIP signalling, IMS core and Android framework path (ImsPhone → ImsService). VoLTE uses an LTE bearer, VoNR uses a 5G SA QoS flow, and VoWiFi reaches the IMS APN through an IPsec tunnel to the ePDG over Wi-Fi. Only the access leg differs.
What is Telecom and what is Telephony in Android?
Telecom (packages/services/Telecomm, in system_server) is a generic call router: it tracks all calls from all calling apps, arbitrates hold/answer between them, manages audio routing and binds the in-call UI. Telephony (frameworks/opt/telephony loaded into com.android.phone) is the cellular stack: Phone objects, call trackers, RIL, service state, data, SIM. Telephony plugs into Telecom via its ConnectionService.
Which process runs CallsManager, TelephonyConnectionService and GsmCdmaCallTracker?
CallsManager runs in system_server (Telecom). TelephonyConnectionService and GsmCdmaCallTracker run in com.android.phone, the persistent phone process. The Dialer and its InCallService run in the Dialer app process.
What does TelecomManager.placeCall() do?
It sends a Binder call to ITelecomService (TelecomServiceImpl) with a tel: URI and extras (for example the chosen PhoneAccountHandle, video state). Telecom checks CALL_PHONE permission and default-dialer status, then routes through UserCallIntentProcessor → CallIntentProcessor → CallsManager.startOutgoingCall().
What is a PhoneAccount?
A Telecom record describing a way to make calls: each SIM subscription registers one (pointing at TelephonyConnectionService), and VoIP apps register their own. It holds capabilities (video, emergency, self-managed), a label and icon, and the ComponentName of the ConnectionService. PhoneAccountRegistrar stores them; Telecom uses them to pick which ConnectionService handles a call.
What is a ConnectionService and a Connection?
A ConnectionService is the android.telecom service a calling stack implements so Telecom can ask it to create outgoing and incoming calls. Each call is a Connection object whose state (dialing, ringing, active, holding, disconnected) and capabilities are reported to Telecom. Telephony's implementations are TelephonyConnectionService and TelephonyConnection.
What is an InCallService?
The android.telecom service that Telecom binds (permission BIND_INCALL_SERVICE) to display and control calls. The default dialer's implementation is the in-call UI; car-mode apps, companion (wearable) apps and non-UI services can also be bound. It receives onCallAdded(), onCallRemoved() and per-call state callbacks, and sends commands (answer, hold, disconnect) back through InCallAdapter.
Where does the decision between CS and IMS happen?
In GsmCdmaPhone.dial(). It evaluates useImsForCall() (IMS enabled, mImsPhone present, VoLTE or WFC or video enabled, IMS in service), plus special rules for MMI/USSD and emergency. If true it calls imsPhone.dial(); otherwise dialInternal() on CS. With the Android 14+ domain selection service, this decision can be delegated to a domain selector.
What is the RIL?
The Radio Interface Layer: the bridge between the telephony framework and the modem. RIL.java (log tag RILJ) implements CommandsInterface, turns calls like dial() into radio HAL requests with a serial number, and receives solicited responses and unsolicited indications. The vendor side (rild) implements the HAL and talks to the modem via QMI, AT or MIPC. See Telephony, RIL & modem.
What is the difference between solicited and unsolicited RIL messages?
Solicited messages are responses to framework requests, matched by serial (for example dialResponse completes the RILRequest for DIAL). Unsolicited messages are modem-initiated events, such as callStateChanged, callRing, signal and network changes, delivered on the indication interface and fanned out through RegistrantLists.
What are the CS call control messages for a mobile-originated call?
After RRC setup and CM SERVICE REQUEST (plus authentication and ciphering): UE → SETUP; network → CALL PROCEEDING; traffic channel assigned; network → ALERTING (ringback); network → CONNECT when answered; UE → CONNECT ACKNOWLEDGE. Clearing uses DISCONNECT → RELEASE → RELEASE COMPLETE.
What is the basic SIP ladder for a VoLTE MO call?
INVITE (SDP offer) → 100 Trying → 183 Session Progress (SDP answer, preconditions) → PRACK → 200 OK (PRACK) → UPDATE → 200 OK (UPDATE) → 180 Ringing → 200 OK (INVITE) → ACK → RTP media. BYE / 200 OK ends it. The dedicated QCI 1 bearer is typically started by the network when the P-CSCF has the SDP answer (around 183), in parallel with PRACK, not as a step that waits for PRACK.
What must be true before an IMS call can be placed?
The IMS PDN must be up, the device must be IMS registered (REGISTER / 401 AKA / 200 OK), VoLTE/VoNR/WFC must be enabled and provisioned for the carrier (CarrierConfig, user toggle, provisioning keys), the vendor ImsService must be bound and its MmTelFeature ready with voice capability, and the network must indicate IMS voice over PS support.
What QoS classes are used for IMS voice?
On LTE, SIP signalling uses QCI 5 (non-GBR, on the IMS default bearer) and voice RTP uses QCI 1 (GBR, dedicated bearer). On 5G SA the equivalents are 5QI 5 and 5QI 1 QoS flows. Video calls add a video bearer (commonly QCI 2).
What happens at a high level when a call comes in?
The network pages the device. CS: the modem reports a new call; GsmCdmaCallTracker polls and creates an INCOMING GsmCdmaConnection. IMS: the vendor ImsService receives a SIP INVITE and calls MmTelFeature.notifyIncomingCall(); ImsPhoneCallTracker creates an ImsPhoneConnection. Either way the phone notifies a new ringing connection, PstnIncomingCallNotifier calls TelecomManager.addNewIncomingCall(), Telecom creates the call and binds the ConnectionService, and the InCallService shows the ringing screen.
What is CSFB?
Circuit-Switched Fallback: a 3GPP procedure for LTE networks without VoLTE. The UE is registered on LTE with a combined attach; for a voice call it sends an EXTENDED SERVICE REQUEST and is moved (by RRC release with redirection, handover or cell change order) to 2G/3G to make a normal CS call, then returns to LTE afterwards. It is done by the modem and network, not by the Android framework.
What does GsmCdmaPhone represent?
The main Phone implementation for one SIM slot, created by PhoneFactory.makeDefaultPhones() together with a RIL instance per slot. It covers GSM, UMTS, LTE, NR and CDMA modes, owns the CS call tracker, service state tracker and SIM records, and owns a child ImsPhone for IMS.
What is ImsPhone and why is it called a shadow phone?
ImsPhone is a second Phone object per slot that represents the IMS domain. It is created and owned by GsmCdmaPhone (mImsPhone), and apps never see it directly; Telecom still talks to the GsmCdmaPhone, which delegates IMS calls to it. Hence "shadow": it sits behind the default phone.
What is the purpose of the vendor ImsService?
It implements AOSP's IMS API (android.telephony.ims.ImsService) so the vendor's SIP/IMS stack can plug into the framework. It provides MmTelFeature (voice/video/SMS over IMS), ImsRegistrationImplBase (registration state) and ImsConfigImplBase (provisioning), and creates call sessions on demand.
What is an emergency call and how is it different from a normal call?
A call to a number recognised by EmergencyNumberTracker. It can be placed without a SIM, in airplane mode (the radio is turned on) and in limited service; Telecom and Telephony bypass restrictions; CS uses emergencyDial (CC EMERGENCY SETUP) and IMS uses an emergency PDN and an INVITE to urn:service:sos. Afterwards the device may enter Emergency Callback Mode.
Which log buffer and dumpsys commands do you start with for a call issue?
adb logcat -b radio for RILJ, call trackers and IMS framework; main/system buffers filtered on Telecom; adb shell dumpsys telecom for the call list and per-call event timeline; dumpsys telephony.registry for call and service state; dumpsys carrier_config for VoLTE settings; and vendor modem logs (QXDM/QCAT or ELT) for NAS/RRC/SIP.
Going deeper
Explain the difference between android.telecom, packages/services/Telecomm and packages/services/Telephony.
android.telecom (source in frameworks/base/telecomm) is the public API: TelecomManager, ConnectionService, InCallService, PhoneAccount. packages/services/Telecomm (com.android.server.telecom) is the Telecom service in system_server: CallsManager, ConnectionServiceWrapper, InCallController. packages/services/Telephony builds com.android.phone; its com.android.services.telephony package is Telephony's ConnectionService implementation, alongside PhoneInterfaceManager and carrier config.
How does Telecom reach TelephonyConnectionService?
The SIM's PhoneAccount names the TelephonyConnectionService component. Call.startCreateConnection() runs CreateConnectionProcessor, which uses ConnectionServiceWrapper to bind the service (it must hold BIND_TELECOM_CONNECTION_SERVICE) and call IConnectionService.createConnection(). Results and state updates come back through IConnectionServiceAdapter.
Is ITelephony / PhoneInterfaceManager on the dial path?
No. Telecom → phone process uses IConnectionService, and TelephonyConnectionService calls Phone.dial() directly in the same process. PhoneInterfaceManager implements ITelephony for TelephonyManager APIs (queries, settings, carrier privileges). An old ITelephony.dial() existed historically but is not how Telecom places calls.
What does TelephonyConnectionService.onCreateOutgoingConnection() do?
It validates the request, resolves the number (including voicemail and MMI), detects emergency numbers, picks the Phone for the requested account (or the best phone for emergency), turns the radio on if needed, applies domain selection where supported, creates a TelephonyConnection (for example GsmConnection) and calls phone.dial(number, dialArgs). The returned internal connection becomes the originalConnection; failures become a disconnected connection with a DisconnectCause.
What is TelephonyConnection's originalConnection?
The internal com.android.internal.telephony.Connection it wraps: a GsmCdmaConnection for CS or an ImsPhoneConnection for IMS. TelephonyConnection listens to it and maps its state to Telecom. During silent redial or SRVCC the originalConnection is replaced while the TelephonyConnection and the Telecom call persist, so the UI sees one continuous call.
Why does GsmCdmaCallTracker poll the call list?
The CS indication callStateChanged has no payload; it just says "something changed". The tracker calls getCurrentCalls(), gets a list of DriverCalls, and handlePollCalls() reconciles it against mConnections[]: new entries become new connections (incoming or MO), changed states update connections, missing entries are disconnects (followed by getLastCallFailCause()). This design handles lost or merged indications robustly.
How is a disconnect cause obtained and shown to the user on a CS call?
When a call disappears from the polled list, the tracker requests getLastCallFailCause() from the RIL. The CallFailCause (3GPP 24.008 cause, e.g. 17 user busy) is mapped by GsmCdmaConnection to an android.telephony.DisconnectCause (e.g. BUSY), then DisconnectCauseUtil converts it to an android.telecom.DisconnectCause with a label, description and tone for the UI.
What is the RIL request/response lifecycle for DIAL?
RIL.dial() obtains a RILRequest with a unique serial and a completion Message, acquires a wakelock, and calls IRadioVoice.dial(serial, dialInfo). rild forwards it to the modem and later calls IRadioVoiceResponse.dialResponse(RadioResponseInfo{serial, error}). RIL finds the request by serial, sends the result to the completion Message (the tracker's EVENT_OPERATION_COMPLETE), and releases the wakelock. Call progress then arrives through indications.
How does an IMS MO call reach the vendor stack?
ImsPhone.dial() → ImsPhoneCallTracker.dial() creates an ImsPhoneConnection and ImsCallProfile → ImsManager.makeCall() asks the MmTelFeature (over Binder) to createCallSession(profile), wraps it in an ImsCall, and calls ImsCall.start() → IImsCallSession.start() → vendor ImsCallSessionImpl, which sends the SIP INVITE through the vendor IMS stack (often in the modem).
How does IMS call state come back to the framework?
The vendor calls ImsCallSessionListener methods (callSessionInitiating, callSessionProgressing, callSessionInitiated/started, callSessionInitiatingFailed, callSessionTerminated). ImsCall converts these to ImsCall.Listener events, ImsPhoneCallTracker updates the ImsPhoneConnection, and TelephonyConnection pushes the new state to Telecom and the UI. There is no polling.
What are ImsResolver and ImsManager responsible for?
ImsResolver finds the ImsService packages (device default from overlay config, or a carrier-specific override from CarrierConfig), binds them per slot and per feature, and rebinds on configuration change or crash. ImsManager is the per-slot framework facade: VoLTE/WFC enablement, makeCall(), takeCall(), and the connection to MmTelFeature. ImsPhone and ImsPhoneCallTracker use ImsManager.
Why are SIP preconditions (183 / PRACK / UPDATE) used?
To make sure the QoS resources for voice (the QCI 1 bearer at both ends) are reserved before the callee is alerted. Otherwise a callee could answer and hear nothing because the bearer is not ready. 183 carries the SDP answer with precondition status (and is when the P-CSCF typically starts dedicated-bearer setup), PRACK acknowledges that 183 reliably, and UPDATE signals that local resources are reserved; only then is 180 Ringing sent.
Who sets up the QCI 1 dedicated bearer?
The network. When the P-CSCF has the SDP answer (typically in 183 Session Progress), it sends media information over Rx to the PCRF (N5 to the PCF on 5G), which installs a PCC rule at the PGW (SMF/UPF on 5G). The network then sends ACTIVATE DEDICATED EPS BEARER CONTEXT REQUEST (or a PDU session modification) to the UE's modem. The Android framework does not request it; it may only see it reported as QoS bearer info in data call updates. Do not pin the start of this bearer to PRACK; PRACK only acknowledges the 183.
What is the difference between 180 Ringing and 183 Session Progress?
180 means the callee is being alerted, so the caller should play or receive ringback. 183 carries session information before the final answer; in VoLTE it carries the SDP answer and precondition negotiation, and it can also carry early media (network announcements or ringback) authorised via P-Early-Media.
How is an IMS call ended, and when is CANCEL used instead of BYE?
ImsCall.terminate() makes the vendor send BYE for an established (answered) dialog; the other side replies 200 OK and the network releases the QCI 1 bearer. If the INVITE has not received a final response yet (still ringing), the vendor sends CANCEL instead and the callee answers the INVITE with 487 Request Terminated.
Describe the CS MT call flow including 3GPP messages.
Network pages the UE; UE sets up RRC and sends PAGING RESPONSE; after authentication/ciphering the network sends SETUP; UE replies CALL CONFIRMED; the modem reports the call to rild → callStateChanged → tracker polls → new INCOMING GsmCdmaConnection → notifyNewRingingConnection() → Telecom → ring UI; UE sends ALERTING. On answer, RIL.acceptCall() → UE sends CONNECT → network CONNECT ACKNOWLEDGE → active.
Describe the IMS MT call flow in the framework.
The vendor ImsService receives an INVITE and calls MmTelFeature.notifyIncomingCall(sessionImpl, extras). ImsPhoneCallTracker's MmTelFeature.Listener.onIncomingCall() calls ImsManager.takeCall() to wrap the session in an ImsCall, creates an INCOMING ImsPhoneConnection in mRingingCall and calls ImsPhone.notifyNewRingingConnection(). From there it is the same Telecom path as CS. Answer calls ImsCall.accept(), which makes the vendor send 200 OK.
What does PstnIncomingCallNotifier do?
It registers with each Phone for new ringing connections (and unknown connections). When one arrives it builds extras (the connection's address, the PhoneAccountHandle of the slot) and calls TelecomManager.addNewIncomingCall(). Telecom then creates the incoming call and asks TelephonyConnectionService.onCreateIncomingConnection() to attach a TelephonyConnection to the ringing internal connection.
How does call screening and blocking fit into the MT flow?
Before ringing, Telecom runs its incoming call filters (IncomingCallFilterGraph): the blocked-number provider, the user's CallScreeningService (default dialer or selected screening app), and DND rules. If a filter rejects the call, Telecom disconnects it through the ConnectionService (CS reject or SIP decline) and logs it without showing the UI.
How does Telecom handle audio for a call?
CallAudioManager sets the audio mode (MODE_RINGTONE while ringing, MODE_IN_CALL for cellular calls, MODE_IN_COMMUNICATION for VoIP), and CallAudioRouteStateMachine handles earpiece, speaker, wired headset and Bluetooth routes. For cellular calls the actual voice processing (vocoder, CS or RTP) happens in the modem/audio DSP; Android configures the path via the audio HAL.
How are call waiting and hold implemented on CS vs IMS?
CS: a waiting call appears in the polled list as WAITING; answering sends switchWaitingOrHoldingAndActive (CHLD=2 style), and hold/resume use the same request; the network handles it with CC hold messages. IMS: hold is a re-INVITE (or UPDATE) with SDP a=sendonly/inactive via ImsCall.hold(), resume uses a=sendrecv; the second MT call is a new ImsPhoneConnection in mRingingCall.
What is useImsForCall() checking, and what else influences IMS usage?
isImsUseEnabled(), mImsPhone != null, VoLTE over cellular or WFC enabled (or video enabled for a video call), and IMS service state IN_SERVICE. Underneath those are CarrierConfig (e.g. carrier VoLTE available), the user's toggles, provisioning status in ImsConfig, the MmTelFeature capability status reported by the vendor, and the network's IMS voice over PS indication.
How is an MMI or USSD code handled when IMS is registered?
GsmCdmaPhone.dial() routes potential USSD codes to ImsPhone if IMS is usable, and supplementary-service MMI codes to IMS if Ut is usable (useImsForUt). If the IMS service does not support the MMI (isSupportedOverImsPhone() false) or processing fails with CS_FALLBACK, ImsPhone throws CallStateException(CS_FALLBACK) and the code is handled on CS.
What are the three or four Binder boundaries crossed on one outgoing call?
App → system_server via ITelecomService; system_server → com.android.phone via IConnectionService (with IConnectionServiceAdapter back); phone → vendor via IRadioVoice (CS, with response/indication callbacks) or IImsMmTelFeature/IImsCallSession (IMS); and system_server → Dialer via IInCallService (with IInCallAdapter back).
How is an emergency call routed differently in the framework?
Telecom flags it as emergency and allows it from any context. TelephonyConnectionService detects the emergency number, powers the radio on with the radio-on helper if needed, picks the best slot (in service over limited service, etc.), and applies emergency domain selection. CS uses RIL.emergencyDial() (IRadioVoice.emergencyDial); IMS uses an emergency ImsCallProfile service type, leading to an emergency PDN and urn:service:sos INVITE. If IMS fails, it retries on CS.
When is the QCI 1 dedicated bearer typically set up on a VoLTE call?
When the P-CSCF has the SDP answer, usually in 183 Session Progress. It then signals the PCRF over Rx (or the PCF over N5) and the network activates the GBR bearer. That often overlaps PRACK, but PRACK is only the reliable acknowledgement of the 183; it is not the trigger. UPDATE later says local preconditions are met, after which 180 Ringing is sent.
Voice is out of service but LTE data works. Can the user place a call?
Often no. ServiceState.getState() is the voice domain; data can stay in service on LTE-only without a CS domain. If IMS is not registered, useImsForCall() is false and the framework tries CS, which then fails. Combined-attach reject EMM #18 is a typical cause. Read both voice and data registration plus IMS registration before calling this an RF problem.
Advanced
What does "CS fallback" mean in the Android framework, and how is it different from radio-level CSFB?
Radio/NAS CSFB is the 3GPP procedure that moves an LTE UE without VoLTE to 2G/3G for voice (EXTENDED SERVICE REQUEST plus redirection or handover); it is invisible to AOSP. Framework IMS → CS fallback is when Telephony tries the call over IMS and, if IMS cannot take it, redials on the CS domain using Phone.CS_FALLBACK (synchronous) or initiateSilentRedial() (asynchronous). A framework CS redial on an LTE-only cell can then trigger radio CSFB in the modem.
Trace the code path of a dial-time (synchronous) CS fallback.
GsmCdmaPhone.dial() checks useImsForCall(); if true it calls imsPhone.dial() inside a try/catch. ImsPhone.dialInternal() may throw new CallStateException(CS_FALLBACK) (for example USSD not supported over IMS). The catch checks Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency; if so it logs "Falling back to CS" and falls through to dialInternal() on CS; otherwise it rethrows the real error.
What is CODE_LOCAL_CALL_CS_RETRY_REQUIRED and what happens when it arrives?
An ImsReasonInfo code meaning the IMS attempt must be retried on CS. It arrives in ImsPhoneCallTracker's onCallStartFailed() after the IMS dial has started. If no call is ringing and the foreground has priority, the tracker detaches and finalizes the pending MO connection, hangs up any lower-priority background call if needed, and calls mPhone.initiateSilentRedial(), which notifies mSilentRedialRegistrants with a SilentRedialParam so the call is redialled on CS. The extra code EXTRA_CODE_CALL_RETRY_EMERGENCY marks it as an emergency retry.
Why is the silent redial posted to the main thread executor?
onCallStartFailed() runs on a Binder callback path and can fire before the main thread has finished the original dial() and linked the new internal connection to the TelephonyConnection. If the redial ran first, the redialled connection could not be associated correctly and would be lost. Posting via mContext.getMainExecutor() orders it after dial() completes.
How does the reverse CS → IMS "VoLTE silent redial" work?
GsmCdmaPhone.notifyVolteSilentRedial(dialString, causeCode) fires mVolteSilentRedialRegistrants. ImsPhone handles EVENT_INITIATE_VOLTE_SILENT_REDIAL by calling its own dial() with updateDialArgsForVolteSilentRedial(), then mDefaultPhone.notifyRedialConnectionChanged(cn) so the TelephonyConnection swaps its originalConnection to the new IMS connection.
How does the Domain Selection Service change call routing?
When DomainSelectionResolver.isDomainSelectionSupported() is true (Android 14+), the CS vs PS decision moves into a domain selection service (NormalCallDomainSelector, EmergencyCallDomainSelector). It considers IMS registration and capabilities, network indications (IMS voice over PS, emergency support), access network, carrier config and failure reasons. On IMS failure, onCallStartFailed() just stores the ImsReasonInfo and disconnects; the selector decides whether and where to redial. This replaces scattered hard-coded logic in the trackers.
How does SRVCC show up in the Android framework?
The network hands an active IMS call to CS; the modem reports SRVCC state through the RIL (srvccStateNotify: started, completed, failed, cancelled). ImsPhoneCallTracker notifies ImsPhone; on completion the IMS connections are transferred: GsmCdmaCallTracker picks up the CS calls and the TelephonyConnections swap their originalConnection from ImsPhoneConnection to GsmCdmaConnection, so the UI call continues. Protocol details are in IMS & VoLTE.
Why is TelephonyConnection separate from the internal Connection classes?
It decouples Telecom's stable public model from Telephony's domain-specific objects. One Telecom call can be backed by different internal connections over its lifetime (IMS to CS by silent redial or SRVCC, CS to IMS by VoLTE silent redial, conference merges). Keeping TelephonyConnection as the adapter means Telecom, the UI and call logs see one continuous call.
What happens when the vendor ImsService crashes during a call?
The Binder death is detected by ImsResolver / the feature connection; features become unavailable, ImsPhone goes out of IMS service and active ImsCalls are terminated (they depend on the vendor session objects), so the call drops with an IMS reason. ImsResolver rebinds, the vendor re-registers, and new calls go CS until IMS is back. On many platforms the SIP stack in the modem may survive, but the framework's session binders are gone.
What happens to calls when rild dies (serviceDied)?
RIL's death recipient completes all pending requests with RADIO_NOT_AVAILABLE, resets the HAL proxies and reconnects when the service restarts. The radio state becomes unavailable, so the trackers treat CS calls as disconnected (cause such as lost signal / radio off). A rild death does not necessarily mean the modem crashed; check for SSR or md1 exception at the same timestamp.
Why is com.android.phone a single point of failure for calling, and how would you harden it?
It hosts every Phone, the RIL clients, the IMS framework and the ConnectionService. An uncaught exception on its main looper kills all of them, dropping calls and radio state; it restarts as a persistent process but with a visible blip. Harden it by never throwing fatally from telemetry or poll paths, catching and recovering in RIL (for example recreating a dead wakelock and retrying), keeping heavy work off the main thread, and making vendor or OEM hooks fail safe.
How is a conference call modelled on IMS vs CS?
CS: multiparty via a conference RIL request (CHLD=3 style); TelephonyConferenceController builds a Telecom Conference from connections in the same GsmCdmaCall. IMS: ImsCall.merge() creates a conference with the network conference server (REFER / conference factory URI); participants are tracked from the conference event package, and ImsConference / ImsConferenceController expose it to Telecom, with participant Connections created from the event info.
How are ImsReasonInfo codes and SIP responses mapped into what the user sees?
The vendor maps SIP responses and local failures to ImsReasonInfo codes (e.g. 486 → CODE_SIP_BUSY, 403 → CODE_SIP_FORBIDDEN). ImsPhoneCallTracker maps those to android.telephony.DisconnectCause and a precise CallFailCause (via maps like PRECISE_CAUSE_MAP). DisconnectCauseUtil then produces the Telecom DisconnectCause shown by the UI and logged in the call history. Carrier config can override some mappings and messages.
How would you explain where the SIP stack lives on MTK vs Qualcomm, and why it matters for debugging?
On Qualcomm the IMS/SIP stack runs on the modem (MPSS); the org.codeaurora.ims APK drives it through the IMS radio HAL and QMI, so SIP traces come from QXDM/QCAT. On MediaTek it is device dependent: older platforms ran IMS daemons on the AP, newer ones run it in the modem, with com.mediatek.ims talking through MTK RIL IMS extensions; SIP traces come from modem logs (ELT) or AP logs accordingly. It matters because you must pull the right log to see the INVITE and its responses.
What happens at the modem and network level when an LTE UE without VoLTE receives a call (MT CSFB)?
The MSC, which has an SGs association with the MME for this UE, sends a paging request over SGs; the MME pages the UE on LTE with a CS domain indicator. The UE sends EXTENDED SERVICE REQUEST (CSFB response: accept), is redirected or handed over to 2G/3G, sends PAGING RESPONSE on the target cell, and the normal CS MT flow (SETUP / CALL CONFIRMED / ALERTING / CONNECT) follows. Android sees only a normal CS incoming call.
How does EPS fallback differ from CSFB and SRVCC?
EPS fallback happens at call setup on 5G SA without VoNR: the network moves the UE from NR to LTE (redirect or handover, using N26 for context) and the call proceeds as VoLTE, still IMS/PS. CSFB is also at call setup but moves from LTE to 2G/3G CS because LTE has no IMS voice. SRVCC happens during an active IMS call and moves it from PS to CS. In the framework, EPS fallback is invisible except for the RAT change and extra setup delay.
How does Telecom arbitrate between a cellular call and a VoIP call?
Both are Telecom Calls from different PhoneAccounts/ConnectionServices. CallsManager enforces rules: only one active call, holding the other when answering (if both support hold), or disconnecting the active one when hold is not supported; emergency calls take priority. Self-managed VoIP apps receive onHold()/onUnhold() and audio focus changes; managed apps are shown in the same InCallUI.
How are dual-SIM (DSDS) calls handled in the call flow?
Each slot has its own Phone, RIL and PhoneAccount; Telecom picks the account (user default, prompt, or per-contact). On DSDS the single radio means a call on one SIM makes the other unreachable (MT calls there are missed unless the network forwards them), and data on the DDS may be suspended or temporarily switched. IMS registration is per slot. For emergency calls TelephonyConnectionService may choose the slot with better service.
What would you do to reduce call setup time on a device?
Measure each segment first with dumpsys telecom event timestamps, radio logs and modem logs (Dialer to DIAL, INVITE to 180, ALERTING to CONNECT). Typical wins: avoid unnecessary silent redials (fix the reason IMS fails), keep IMS registered and the IMS PDN up, tune preconditions and timers with the carrier, avoid EPS fallback or CSFB where VoNR/VoLTE is available, and remove slow work (contact lookup, OEM hooks) from Telecom's critical path.
Why is ALERTING shown as DIALING in Telecom, and how does the UI know to play ringback?
Telecom's Connection state model has no separate alerting state; both states are STATE_DIALING. Ringback on CS is normally provided in-band by the network after ALERTING. On IMS, if the network sends early media (183 with P-Early-Media), the device plays it; otherwise, on 180 without early media, the device generates local ringback. Telephony exposes this through Connection extras / ringback events so the audio path is set correctly.
Scenario & debugging
An outgoing call fails immediately with "Call not sent". How do you debug it?
Check whether a DIAL request (CS) or INVITE (IMS) was ever sent. If not, the failure is above the radio: look at Telecom logs (no valid PhoneAccount, emergency-only restriction), TelephonyConnectionService (out of service, airplane mode, FDN), and any CallStateException in GsmCdmaPhone.dial(). If DIAL was sent, read the RIL response error and LAST_CALL_FAIL_CAUSE (e.g. FDN_BLOCKED, CALL_BARRED, 34 no circuit). For IMS, read onCallStartFailed's ImsReasonInfo and the SIP response.
The VoLTE icon is shown but calls go over CS. What do you check?
Look for "Trying (non-IMS) CS call" and evaluate useImsForCall(): is the IMS service state really IN_SERVICE, VoLTE enabled and provisioned, and does the MmTelFeature report voice capability? The icon may be stale. Then check whether IMS was tried and fell back ("Falling back to CS", CODE_LOCAL_CALL_CS_RETRY_REQUIRED). On the network side verify IMS voice over PS support in the attach, IMS PDN and P-CSCF, re-REGISTER timing, and SIP 380/503 responses.
A specific number always drops from VoLTE to a CS call. How do you debug it?
Capture logcat -b radio and look for "Falling back to CS" and onCallStartFailed with CODE_LOCAL_CALL_CS_RETRY_REQUIRED. Correlate with the modem/SIP trace: a SIP failure (488, 503, 380) or a local modem reason? Check whether the number is USSD/MMI-like (legitimately CS), an emergency or special short code routed CS by carrier config, or rejected by the carrier's TAS. That tells you whether the network rejected VoLTE for that number or the device chose CS.
An IMS call fails with SIP 488 Not Acceptable Here on one carrier. What do you check?
488 means the SDP offer was not acceptable: codec, bandwidth, precondition or media attribute mismatch. Compare the INVITE's SDP with the carrier's requirements (AMR-WB vs EVS modes, payload types, a=curr/des/conf precondition lines, bandwidth, DTMF telephone-event), and compare with a working carrier's INVITE. Fixes are usually in carrier config or vendor IMS configuration (codec lists, AMR mode sets).
The caller hears ringing, but the callee's phone never rings. Where do you look?
Ringback means the originating side got 180 or early media, so check whether it came from the callee or was network-generated. On the terminating side: IMS registration state, whether paging succeeded, whether the INVITE reached the UE (terminating modem log), precondition completion, and TAS diversion or barring. On the callee's framework: did notifyIncomingCall arrive, did Telecom's filters (blocking, CallScreeningService, DND) silently reject it, or was the ringtone suppressed?
Incoming calls go straight to voicemail. How do you debug?
Check reachability first: was the device registered (IMS and CS) and in coverage at that time, and did paging arrive (modem log)? If no page, it is network or coverage (or call forwarding on not-reachable). If the call reached the device, check Telecom's dumpsys telecom for a rejected or filtered call (blocked number, screening app, DND), a DSDS conflict (other SIM in a call), or an automatic reject due to a framework crash in com.android.phone.
A VoLTE call connects but has one-way audio. How do you root-cause it?
Determine which direction is missing, then follow the media path: SDP addresses and ports on both sides, whether the QCI 1 bearer and its TFT are correct in both directions, codec negotiation, RTP packet counters in modem logs, NAT or firewall in the network, and on the device the audio mode and route (MODE_IN_CALL, mic mute state, Bluetooth routing). If RTP flows in both directions at the modem but audio is missing, look at the AP audio path and DSP.
A user reports silent call drops. What is your triage order?
First check chipset/log family, RF environment and modem health. Then: modem reset near the drop (SSR, md1 exception, serviceDied) is a strong lead; otherwise find the protocol cause at the drop timestamp (SIP BYE with Reason, RRC release, radio link failure, NAS cause, CallFailCause); check the RF trend (RSRP/RSRQ, handover attempts, SRVCC); correlate framework, radio and modem logs by time; and build a hypothesis vs evidence table before concluding.
Calls drop when the user walks out of the building. What is happening and how do you confirm it?
Likely a mobility failure: SRVCC to 2G/3G failing, VoLTE coverage loss with no SRVCC configured, or a failed LTE ↔ Wi-Fi handover if the call was VoWiFi. Confirm with measurement reports and handover commands in modem logs, srvccStateNotify in radio logs, and the SIP trace (BYE or session loss). For VoWiFi, check whether the IMS PDN on LTE was requested as a handover (same IP) or as an initial attach (new IP breaks the SIP dialog).
Call drops mid-call and the logs show RIL serviceDied. Is the modem dead?
Not necessarily. serviceDied means the radio HAL service process (rild) died. Look for a modem crash at the same time (QCOM SSR / ramdump, MTK md1 exception). If there is none, the vendor daemon crashed (check its tombstone). If the modem did crash, the call drop is a consequence and the modem crash signature is the root-cause lead.
com.android.phone keeps crashing and calls drop. How do you approach it?
Get the FATAL EXCEPTION stack from logcat and the crash buffer, and identify the real owner (the phone process hosts several packages, so crash labels can blame the wrong one). Determine the path (RIL, IMS, OEM hook, telemetry poll). Check modem health to rule it out. Fix by making the failing path recover instead of throwing on the main thread (e.g. recreate a dead wakelock and retry), and add guards in any non-essential code that runs in the phone process.
Call setup takes 8 to 10 seconds. How do you find where the time goes?
Build a timeline: Dialer tap → Telecom call created (dumpsys telecom events) → TelephonyConnectionService → "Trying IMS PS call" → INVITE → 100 / 183 / 180 → 200 OK. Look for silent redial (IMS attempt failing then CS), radio CSFB or EPS fallback (RAT change in ServiceStateTracker), slow preconditions (bearer setup), IMS re-registration before the call, or slow work in Telecom (contact lookup, call redirection). Fix the largest gap first.
An emergency call fails in airplane mode. What do you check?
Was the number recognised as emergency (EmergencyNumberTracker sources for the country/SIM)? Did the radio-on helper power the radio and did it time out waiting for service? Which slot and domain were chosen, and did the IMS emergency attempt fail without a CS retry? Check modem logs for camping in limited service and for EMERGENCY SETUP or the emergency PDN / urn:service:sos INVITE, and carrier config for emergency domain preferences.
A user cannot make VoLTE calls after a SIM swap, but data works. What could be wrong?
The new carrier may need a different ImsService binding or config: check cmd phone ims get-ims-service, CarrierConfig for the new carrier (VoLTE available, provisioning required), provisioning status, and whether the IMS PDN (IMS APN) comes up and gets a P-CSCF. Check for a SIP 403 on REGISTER (subscription not VoLTE-enabled), or ISIM/IMPI issues. Data working only proves the default PDN.
After answering an incoming call, the UI shows "active" but there is no audio at all. What do you check?
For IMS: did the 200 OK / ACK complete, is the QCI 1 bearer up, is RTP flowing in modem logs, and did the SDP answer match the offer? For CS: did CONNECT / CONNECT ACK complete and was a traffic channel assigned? On the AP: audio mode transition to MODE_IN_CALL, audio route (Bluetooth device connected but SCO not up is common), mic mute, and audio HAL / DSP errors in logs.
Calls on SIM 2 fail but SIM 1 works on the same device. How do you narrow it down?
A single-slot failure suggests slot or subscription specifics, not the platform. Compare PhoneAccount registration for slot 2 in dumpsys telecom, service state and IMS registration for phone 1 ([PHONE1] in RILJ), carrier config for that subscription, and whether a DSDS constraint (the other SIM holding the radio for data or a call) is involved. Then read the cause codes for slot 2's attempts.
Users report the in-call screen does not appear, but the call is active. Where do you look?
The call exists in Telecom, so the issue is the InCallService binding. In dumpsys telecom check InCallController: is the default dialer's in-call service bound, did the bind fail or time out, is the default dialer set correctly, or was a car-mode or third-party dialer bound instead? Check for crashes or ANRs in the dialer process.
Holding a VoLTE call fails and the call drops. What do you check?
Hold is a re-INVITE (or UPDATE) with a=sendonly / inactive. Look at the response: a 491 (request pending, glare), 488 (SDP not acceptable) or timeout. Check that the ImsStreamMediaProfile direction was set correctly, that the carrier supports the chosen hold method, and whether the call was terminated with a specific ImsReasonInfo after the hold failure. Compare with carrier config around hold and resume handling.
A merge into a conference call fails on IMS. How do you debug?
Trace ImsCall.merge(): was the conference factory URI configured, did the INVITE to it succeed, did the REFERs to add participants get 202, and did the conference event package NOTIFY arrive? Check ImsPhoneCallTracker merge callbacks (onCallMerged vs onCallMergeFailed) and the ImsReasonInfo. Also check carrier config for conference support and participant limits.
A call drops right after a VoWiFi to VoLTE move. What is the likely cause?
A broken IP anchor: the IMS PDN on LTE was set up as an initial request instead of a handover, so the UE got a new IP and the SIP dialog was lost. Check the request type in the PDN connectivity request, whether the PGW preserved the IP, whether IMS was registered on the target before the source path was torn down, and the timing of the re-INVITE or UPDATE that moves media. Correlate IWLAN/IKEv2, NAS and SIP traces.
Two different builds behave differently: one makes VoLTE calls and one does not. How do you approach it?
A build-specific repro points to a regression. Diff the ImsService version, carrier config overlays, radio and IMS HAL versions, and framework changes in GsmCdmaPhone/ImsPhoneCallTracker/domain selection. Compare radio logs side by side from the same SIM and location: where does the failing build diverge (registration, useImsForCall() inputs, INVITE content, response)? Bisect if needed.
An interviewer shows you "IMS_RILA: serviceProxy == null" thousands of times and says the modem is down. How do you respond?
Treat it as a symptom, not a cause. A null service proxy means the IMS radio HAL service was never bound or died. Check for a modem crash (SSR, md1) and RIL serviceDied; if none, look at init and VINTF: a HAL declared in the manifest but missing an init service entry (lazy HAL start failing, "Could not find ... for ctl.interface_start") explains it. The modem may be perfectly healthy; the AP-side HAL startup is broken.