Мы часто видим, как 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. Тестирование в эмуляторе 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.
Процесс и сроки
Реализация 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. Стоимость рассчитывается индивидуально после анализа существующего VoIP-стека и требований.
Что входит в работу
- Реализация ConnectionService с поддержкой входящих и исходящих звонков
- Регистрация PhoneAccount и настройка URI-схем
- Обработка аудиофокуса и маршрутизации (динамик, Bluetooth, наушники)
- Интеграция с системным журналом звонков
- Тестирование на 5+ реальных устройствах (Samsung, Xiaomi, OPPO, Pixel)
- Документация по эксплуатации и поддержка 2 недели после сдачи
Свяжитесь с нами для бесплатной оценки проекта. Закажите интеграцию под ключ — получите готовое решение с гарантией работоспособности.
Подробнее в официальной документации: Android Developer Docs — Android Telecom Framework.







