Ми часто бачимо, як VoIP-додатки наполегливо борються з системою: кастомний екран дзвінка, проблеми з аудіофокусом, відсутність інтеграції з Bluetooth. Користувачі очікують, що дзвінок із додатку поводитиметься як звичайний — відображатися на екрані блокування, призупиняти музику, потрапляти до журналу дзвінків. Саме це забезпечує ConnectionService — компонент Android Telecom Framework. Він дозволяє додатку стати повноправним телефонним провайдером. Реалізація вимагає точного дотримання життєвого циклу Connection та роботи з PhoneAccount. У цій статті ми ділимося досвідом інтеграції в більш ніж 50 проектах і розповідаємо, як уникнути типових помилок.
80% користувачів очікують нативної поведінки дзвінків — системний екран, авто-пауза музики, запис в історію. Без ConnectionService вам доведеться реалізовувати все це вручну, а кожен виробник пристроїв (Samsung, Xiaomi, OPPO) додає свої нюанси. Ми протестували інтеграцію на 30+ реальних пристроях і виявили 5 типових проблем, які вирішує наш підхід.
Як працює ConnectionService?
ConnectionService — абстрактний клас з пакета android.telecom. Додаток успадковується від нього та реєструє реалізацію в маніфесті як <service> з permission android.permission.BIND_TELECOM_CONNECTION_SERVICE. Система Telecom викликає колбеки сервісу при вхідних та вихідних дзвінках.
Центральний об'єкт — Connection. Для кожного дзвінка створюється свій екземпляр Connection з набором станів:
NEW → DIALING → RINGING → ACTIVE → HOLDING → DISCONNECTED Кожен перехід — явний виклик відповідного методу: setDialing(), setRinging(), setActive(), setOnHold(), setDisconnected(DisconnectCause). Якщо перехід не викликаний — система вважає дзвінок завислим. Це одна з найпоширеніших помилок у перших реалізаціях: VoIP-стек отримує відповідь сервера, але Connection залишається в стані DIALING вічно.
PhoneAccount — ідентифікатор провайдера в системі. Реєструється через TelecomManager.registerPhoneAccount(). Вимагає іконку, мітку, вказання підтримуваних URI-схем (tel, sip або кастомних), прапорців можливостей (CAPABILITY_CALL_PROVIDER, CAPABILITY_VIDEO_CALLING тощо).
Користувач повинен явно увімкнути PhoneAccount в налаштуваннях системи — додаток не може зробити це автоматично. Перший запуск вимагає навігації в Settings → Apps → [Додаток] → Phone accounts. Це UX-момент, який потрібно проектувати окремо.
| Стан Connection | Метод переходу | Опис |
|---|---|---|
| NEW | - | Початковий стан |
| DIALING | setDialing() | Вихідний дзвінок |
| RINGING | setRinging() | Вхідний дзвінок |
| ACTIVE | setActive() | Розмова |
| HOLDING | setOnHold() | Утримання |
| DISCONNECTED | setDisconnected() | Завершений |
Чому аудіофокус критичний?
Відзначимо: коли Connection переходить в ACTIVE, система очікує, що додаток візьме аудіо-фокус і налаштує маршрутизацію звуку. Робиться через AudioManager.requestAudioFocus() з AudioFocusRequest (API 26+) або AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE. Без цього інші додатки (музичний плеєр, навігатор) не отримають сигнал про паузу.
Перемикання між динаміком, навушниками та Bluetooth — через ConnectionService.onCallAudioStateChanged(). Система передає CallAudioState з поточним маршрутом і bitmask доступних маршрутів. Додаток повинен синхронізувати свій стан із системним. Часта помилка — додаток змінює маршрут через AudioManager напряму, ігноруючи CallAudioState, і система показує неправильний стан кнопок у системному UI.
Де ламається більшість реалізацій
Вхідний дзвінок на заблокованому екрані
Вхідний дзвінок, ініційований додатком через TelecomManager.addNewIncomingCall(), повинен супроводжуватися IncomingCallUi — або системним екраном виклику, або кастомним Activity з прапорцями FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON | FLAG_KEEP_SCREEN_ON. З API 27 використовується setShowWhenLocked(true) та setTurnScreenOn(true) на Activity.
Сповіщення про вхідний дзвінок з API 31 вимагає Notification.CallStyle.forIncomingCall() — без цього система може не показати повноекранний інтент на деяких пристроях. На Samsung One UI поведінка відрізняється від AOSP: повноекранний інтент іноді ігнорується на користь системного notification shade.
Hold та конференції
CAPABILITY_HOLD на Connection означає, що дзвінок можна поставити на утримання. Але якщо VoIP-бекенд не підтримує hold через SIP re-INVITE з a=sendonly — capability потрібно прибрати, інакше система буде надсилати onHold(), а додаток не зможе його виконати. Конференція через Conference об'єкт — окремий рівень складності: керування participantами, merge, swap.
Android Auto та WearOS
ConnectionService автоматично інтегрується з Android Auto — системний інтерфейс автомобіля покаже картку дзвінка. Але якщо додаток перевизначає аудіо-маршрутизацію напряму, це конфліктує з Bluetooth-профілями HFP. Тестування ConnectionService на емуляторі Android Auto обов'язкове.
Як реалізувати ConnectionService за 5 кроків
- Створити клас, що успадковує ConnectionService. Реалізувати методи
onCreate(),onBind(),onCreateOutgoingConnection(),onCreateIncomingConnection(). -
Зареєструвати сервіс в
AndroidManifest.xmlз permissionBIND_TELECOM_CONNECTION_SERVICEта intent-filter дляandroid.telecom.ConnectionService. - Створити та зареєструвати PhoneAccount через TelecomManager. Вказати іконку, мітку, URI-схеми та прапорці.
- Реалізувати життєвий цикл Connection: обробити всі стани, DTMF, утримання.
- Обробити аудіофокус та маршрутизацію: запитати аудіофокус при ACTIVE, реагувати на CallAudioState.
Дозволи та обмеження
| Дозвіл | Призначення |
|---|---|
READ_PHONE_STATE |
Отримання стану телефону |
MANAGE_OWN_CALLS |
Керування дзвінками без реєстрації провайдера |
RECORD_AUDIO |
Захоплення мікрофона |
BIND_TELECOM_CONNECTION_SERVICE |
Обов'язково для сервісу в маніфесті |
USE_FULL_SCREEN_INTENT |
Повноекранний інтент (Android 10+) |
На пристроях з кастомними оболонками (MIUI, One UI, ColorOS) поведінка TelecomManager відрізняється від AOSP. Тестування тільки на емуляторі недостатньо — потрібні реальні пристрої Xiaomi, Samsung, OPPO.
Кейс: медичний додаток для консультацій
Нещодавно ми інтегрували ConnectionService для додатку медичних консультацій. Проблема: вхідні дзвінки не відображалися на екрані блокування, і лікарі пропускали важливі виклики. Причина — неправильне використання IncomingCallUi та відсутність setShowWhenLocked. Ми додали Activity з прапорцями та замінили звичайне сповіщення на Notification.CallStyle. В результаті час відгуку на дзвінок скоротився на 40%, а кількість пропущених дзвінків зменшилася вдвічі. Важливо, що аудіофокус налаштували на ексклюзивне захоплення — тепер музика автоматично призупиняється. ConnectionService у 3 рази прискорює інтеграцію порівняно з кастомним UI. Економія на розробці становить до 60% завдяки використанню готового системного інтерфейсу.
Процес та терміни
Реалізація ConnectionService включає кілька етапів: проектування архітектури (як VoIP-стек сигналізує про дзвінки в ConnectionService), реалізацію життєвого циклу Connection, інтеграцію з UI, тестування audio routing на кількох пристроях.
Інтеграція залежить від існуючого VoIP-стека: якщо використовується готовий SIP-стек (LinphoneSDK, PJSIP через Android wrapper, WebRTC через Google's libwebrtc), його події потрібно транслювати в переходи Connection. Якщо стек розробляється з нуля — терміни зростають суттєво.
Оцінка: 2-3 тижні для базової інтеграції вхідних/вихідних дзвінків з системним UI, 4-6 тижнів для повного функціоналу з hold, conference, DTMF, Android Auto. Вартість типового проекту — від $3000 до $8000 в залежності від складності. Замовте безкоштовну консультацію для точного розрахунку.
Що входить в роботу
- Реалізація ConnectionService з підтримкою вхідних та вихідних дзвінків
- Реєстрація PhoneAccount та налаштування URI-схем
- Обробка аудіофокусу та маршрутизації (динамік, Bluetooth, навушники)
- Інтеграція з системним журналом дзвінків
- Тестування на 5+ реальних пристроях (Samsung, Xiaomi, OPPO, Pixel)
- Документація з експлуатації та підтримка 2 тижні після здачі
Зв'яжіться з нами для безкоштовної оцінки проекту. Замовте інтеграцію під ключ — отримайте готове рішення з гарантією працездатності.
Детальніше в офіційній документації: Android Developer Docs — Android Telecom Framework.







