We often see VoIP apps struggling with the system: custom call screen, audio focus issues, missing Bluetooth integration. Users expect app calls to behave like regular calls — appear on the lock screen, pause music, and appear in the call log. This is exactly what ConnectionService — a component of Android Telecom Framework — provides. It allows your app to become a full-fledged phone provider. Implementation requires precise adherence to the Connection lifecycle and working with PhoneAccount. In this article, we share experience from over 50 projects and explain how to avoid common pitfalls.
80% of users expect native call behavior — system screen, auto-pause music, history recording. Without ConnectionService, you have to implement all this manually, and each device manufacturer (Samsung, Xiaomi, OPPO) adds its own quirks. We tested integration on 30+ real devices and identified 5 typical problems that our approach solves.
How ConnectionService Works
ConnectionService is an abstract class from the android.telecom package. Your app inherits from it and registers the implementation in the manifest as a <service> with permission android.permission.BIND_TELECOM_CONNECTION_SERVICE. The Telecom system calls the service callbacks for incoming and outgoing calls.
The central object is Connection. For each call, a separate Connection instance is created with a set of states:
NEW → DIALING → RINGING → ACTIVE → HOLDING → DISCONNECTED Each transition requires an explicit call to the corresponding method: setDialing(), setRinging(), setActive(), setOnHold(), setDisconnected(DisconnectCause). If a transition is not invoked, the system considers the call stuck. This is one of the most common mistakes in initial implementations: the VoIP stack receives the server response, but the Connection remains in DIALING forever.
PhoneAccount — the provider identifier in the system. It is registered via TelecomManager.registerPhoneAccount(). It requires an icon, label, supported URI schemes (tel, sip, or custom), and capability flags (CAPABILITY_CALL_PROVIDER, CAPABILITY_VIDEO_CALLING, etc.).
The user must explicitly enable the PhoneAccount in system settings — the app cannot do this automatically. The first launch requires navigating to Settings → Apps → [App] → Phone accounts. This is a UX aspect that needs separate design.
| Connection State | Transition Method | Description |
|---|---|---|
| NEW | - | Initial state |
| DIALING | setDialing() | Outgoing call |
| RINGING | setRinging() | Incoming call |
| ACTIVE | setActive() | Conversation |
| HOLDING | setOnHold() | Hold |
| DISCONNECTED | setDisconnected() | Ended |
Why Audio Focus Is Critical
Note: when Connection transitions to ACTIVE, the system expects the app to take audio focus and configure audio routing. This is done via AudioManager.requestAudioFocus() with AudioFocusRequest (API 26+) or AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE. Without this, other apps (music player, navigation) won't receive a pause signal.
Switching between speaker, headphones, and Bluetooth — via ConnectionService.onCallAudioStateChanged(). The system passes CallAudioState with the current route and a bitmask of available routes. The app must synchronize its state with the system. A common mistake is that the app changes the route directly via AudioManager, ignoring CallAudioState, and the system shows incorrect button states in the system UI.
Where Most Implementations Break
Incoming Call on Lock Screen
An incoming call initiated by the app via TelecomManager.addNewIncomingCall() must be accompanied by an IncomingCallUi — either the system call screen or a custom Activity with flags FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_KEEP_SCREEN_ON. From API 27, use setShowWhenLocked(true) and setTurnScreenOn(true) on the Activity.
For incoming call notifications from API 31, Notification.CallStyle.forIncomingCall() is required — without it, the system might not show a full-screen intent on some devices. On Samsung One UI, behavior differs from AOSP: the full-screen intent is sometimes ignored in favor of the system notification shade.
Hold and Conference
CAPABILITY_HOLD on Connection means the call can be put on hold. But if the VoIP backend does not support hold via SIP re-INVITE with a=sendonly — the capability must be removed, otherwise the system will send onHold(), and the app will be unable to execute it. Conference via the Conference object is a separate complexity: managing participants, merge, swap.
Android Auto and WearOS
ConnectionService automatically integrates with Android Auto — the in-car system interface will show a call card. But if the app overrides audio routing directly, it conflicts with HFP Bluetooth profiles. Testing in the Android Auto emulator is mandatory.
How to Implement ConnectionService in 5 Steps
- Create a class extending ConnectionService. Implement methods
onCreate(),onBind(),onCreateOutgoingConnection(),onCreateIncomingConnection(). -
Register the service in
AndroidManifest.xmlwith permissionBIND_TELECOM_CONNECTION_SERVICEand intent-filter forandroid.telecom.ConnectionService. - Create and register a PhoneAccount via TelecomManager. Specify icon, label, URI schemes, and flags.
- Implement the Connection lifecycle: handle all states, DTMF, hold.
- Handle audio focus and routing: request audio focus when ACTIVE, react to CallAudioState.
Permissions and Limitations
| Permission | Purpose |
|---|---|
READ_PHONE_STATE |
Get phone state |
MANAGE_OWN_CALLS |
Manage calls without registering provider |
RECORD_AUDIO |
Capture microphone |
BIND_TELECOM_CONNECTION_SERVICE |
Mandatory for service in manifest |
USE_FULL_SCREEN_INTENT |
Full-screen intent (Android 10+) |
On devices with custom skins (MIUI, One UI, ColorOS), TelecomManager behavior differs from AOSP. Testing only on emulator is insufficient — real Xiaomi, Samsung, OPPO devices are needed.
Case Study: Medical Consultation App
Recently we integrated ConnectionService for a medical consultation app. The problem: incoming calls were not displayed on the lock screen, causing doctors to miss important calls. The reason was incorrect use of IncomingCallUi and missing setShowWhenLocked. We added an Activity with flags and replaced the ordinary notification with Notification.CallStyle. As a result, call response time decreased by 40%, and missed calls were halved. Importantly, we configured audio focus for exclusive capture — now music automatically pauses. ConnectionService accelerates integration by 3x compared to custom UI.
Process and Timeline
ConnectionService implementation includes several stages: architecture design (how the VoIP stack signals calls to ConnectionService), implementation of the Connection lifecycle, UI integration, audio routing testing on multiple devices.
Integration depends on the existing VoIP stack: if using a ready-made SIP stack (LinphoneSDK, PJSIP via Android wrapper, WebRTC via Google's libwebrtc), its events need to be translated into Connection transitions. If the stack is being developed from scratch, timelines increase significantly.
Estimation: 2-3 weeks for basic integration of incoming/outgoing calls with system UI, 4-6 weeks for full functionality with hold, conference, DTMF, Android Auto. Cost is calculated individually after analyzing the existing VoIP stack and requirements.
What's Included
- ConnectionService implementation supporting incoming and outgoing calls
- PhoneAccount registration with URI scheme configuration
- Audio focus and routing handling (speaker, Bluetooth, headphones)
- Integration with system call log
- Testing on 5+ real devices (Samsung, Xiaomi, OPPO, Pixel)
- Operations documentation and 2 weeks post-delivery support
Contact us for a free project assessment. Order a turnkey integration — get a ready-to-use solution with a guarantee of functionality.
For more details, see official documentation: Android Developer Docs — Android Telecom Framework.







