Библиотека Core-Telecom упрощает интеграцию приложения для звонков с платформой Android, предоставляя надежный и согласованный набор API.
Если вы хотите изучить практические реализации, на GitHub можно найти примеры приложений:
- Lightweight Sample App – минимальный пример использования API
Core-Telecom. Идеально подходит для быстрого изучения основных понятий. - Пример расширенного приложения (разработано командой Core-Telecom) – приложение с большим количеством функций, демонстрирующее расширенные возможности Telecom и лучшие практики. Это отличный ресурс для понимания сложных сценариев интеграции.
Как настроить Core-Telecom
Добавьте зависимость androidx.core:core-telecom в файл build.gradle своего приложения:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0")
}
Задекларируйте разрешение MANAGE_OWN_CALLS в файле AndroidManifest.xml:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Регистрация приложения
Зарегистрируйте приложение для звонков в Android с помощью CallsManager, чтобы начать добавлять звонки в систему. При регистрации укажите возможности приложения (например, поддержку аудио и видео):
val callsManager = CallsManager(context)
val capabilities: @CallsManager.Companion.Capability Int =
(CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)
callsManager.registerAppWithTelecom(capabilities)
Управление звонками
Используйте Core-Telecom API для создания и управления жизненным циклом вызова.
Как создать звонок
Объект CallAttributesCompat определяет свойства уникального звонка, который может обладать следующими характеристиками:
displayName– имя звонящего.address– адрес для звонка (например, номер телефона или ссылка на встречу).direction– входящий или исходящий.callType– аудио или видео.callCapabilities– поддерживает перевод и удержание вызова.
Вот пример того, как создать входящий звонок:
fun createIncomingCallAttributes(
callerName: String,
callerNumber: String,
isVideoCall: Boolean): CallAttributesCompat {
val addressUri = Uri.parse("YourAppScheme:$callerNumber")
return CallAttributesCompat(
displayName = callerName,
address = addressUri,
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = if (isVideoCall) {
CallAttributesCompat.CALL_TYPE_VIDEO_CALL
} else {
CallAttributesCompat.CALL_TYPE_AUDIO_CALL
},
callCapabilities = CallAttributesCompat.SUPPORTS_SET_INACTIVE
)
}
Как добавить звонок
Используйте callsManager.addCall с CallAttributesCompat и обратными вызовами, чтобы добавить в систему новый вызов и управлять обновлениями удаленной поверхности. Тег callControlScope внутри блока addCall позволяет приложению переходить в состояние звонка и получать аудиообновления:
try {
callsManager.addCall(
INCOMING_CALL_ATTRIBUTES,
onAnswerCall, // Watch needs to know if it can answer the call.
onSetCallDisconnected,
onSetCallActive,
onSetCallInactive
) {
// The call was successfully added once this scope runs.
callControlScope = this
}
}
catch(addCallException: Exception){
// Handle the addCall failure.
}
Как ответить на звонок
Ответить на входящий вызов в течение CallControlScope.
when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
is CallControlResult.Success -> { /* Call answered */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Как отклонить звонок
Отклонить вызов, используя disconnect() с DisconnectCause.REJECTED в CallControlScope:
disconnect(DisconnectCause(DisconnectCause.REJECTED))
Как сделать исходящий вызов активным
Чтобы исходящий вызов стал активным, когда абонент на другом конце ответит:
when (val result = setActive()) {
is CallControlResult.Success -> { /* Call active */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Удержание вызова
Чтобы поставить вызов на удержание, используйте номер setInactive():
when (val result = setInactive()) {
is CallControlResult.Success -> { /* Call on hold */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Как отключить вызов
Чтобы завершить вызов с помощью disconnect() на устройстве DisconnectCause:
disconnect(DisconnectCause(DisconnectCause.LOCAL))
Как управлять конечными точками аудио для звонков
Отслеживайте и управляйте аудиоконечными точками с помощью currentCallEndpoint, availableEndpoints и isMuted Flow в CallControlScope. Не используйте API AudioManager#setCommunicationDevice или AudioManager#startBluetoothSco для управления аудиомаршрутами при использовании Telecom, так как это может привести к проблемам со звуком во время звонка.
fun observeAudioStateChanges(callControlScope: CallControlScope) {
with(callControlScope) {
launch { currentCallEndpoint.collect { /* Update UI */ } }
launch { availableEndpoints.collect { /* Update UI */ } }
launch { isMuted.collect { /* Handle mute state */ } }
}
}
Чтобы изменить активное аудиоустройство, используйте requestEndpointChange():
coroutineScope.launch {
callControlScope.requestEndpointChange(callEndpoint)
}
Поддержка активного режима
Библиотека использует ConnectionService в Android 13 (уровень API 33) и более ранних версиях или типы активных служб в Android 14 (уровень API 34) и более поздних версиях для поддержки активных служб.
Чтобы звонки не прерывались, когда приложение работает в фоновом режиме, разместите CallsManager
в активной службе Service (например, LifecycleService) и объявите тип активной службы phoneCall в манифесте AndroidManifest.xml:
<service
android:name=".TelecomVoipService"
android:foregroundServiceType="phoneCall" />
Согласно требованиям к активному режиму, ваше приложение должно показывать уведомление NotificationCompat.CallStyle, чтобы пользователи знали, что звонок выполняется в активном режиме. Чтобы обеспечить приоритет выполнения приложения в активном режиме, переведите службу в активный режим с помощью метода startForeground после того, как добавите вызов с платформой:
startForeground(
notificationId,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
)
Подробнее об активных службах…
Удаленная поддержка устройств Surface
Удаленные устройства (умные часы, Bluetooth-гарнитуры, Android Auto) могут управлять звонками без прямого взаимодействия с телефоном. В приложении должны быть реализованы лямбда-функции обратного вызова (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive), предоставленные CallsManager.addCall, для обработки действий, инициированных этими устройствами.
Когда происходит удаленное действие, вызывается соответствующая функция Lambda.
Успешное выполнение функции Lambda означает, что команда была обработана. Если команду выполнить невозможно, лямбда-функция должна вызвать исключение.
Правильная реализация обеспечивает удобное управление вызовами на разных устройствах. Протестируйте приложение на разных устройствах.
Номера телефонов
Библиотека позволяет не только управлять статусом вызова и маршрутом передачи аудио, но и использовать расширения для звонков – необязательные функции, которые можно реализовать в приложении, чтобы улучшить качество связи на внешних устройствах, например в Android Auto. В частности, в нем есть конференц-залы, беззвучный режим и дополнительные значки звонков. Если в приложении реализовано расширение, информация, которую оно предоставляет, будет синхронизироваться со всеми подключенными устройствами, которые также поддерживают показ этих расширений в своем интерфейсе. Это означает, что эти функции будут доступны на удаленных устройствах, с которыми пользователи смогут взаимодействовать.
Как создать звонок с расширениями
При создании звонка вместо CallsManager.addCall можно использовать CallsManager.addCallWithExtensions, чтобы предоставить приложению доступ к другой области действия под названием ExtensionInitializationScope. Этот диапазон позволяет приложению инициализировать набор поддерживаемых им дополнительных расширений. Кроме того, этот диапазон предоставляет дополнительный метод onCall, который возвращает CallControlScope в приложение после завершения обмена данными и инициализации возможностей расширения.
scope.launch {
mCallsManager.addCallWithExtensions(
attributes,
onAnswer,
onDisconnect,
onSetActive,
onSetInactive
) {
// Initialize extension-specific code...
// After the call has been initialized, perform in-call actions
onCall {
// Example: process call state updates
callStateFlow.onEach { newState ->
// handle call state updates and notify telecom
}.launchIn(this)
// Use initialized extensions...
}
}
}
Поддержка участников звонка
Если ваше приложение поддерживает участников звонков для встреч или групповых звонков, используйте addParticipantExtension, чтобы заявить о поддержке этого расширения, и используйте связанные API для обновления удаленных поверхностей при изменении участников.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Notifies Jetpack that this app supports the participant
// extension and provides the initial participants state in the call.
val participantExtension = addParticipantExtension(
initialParticipants,
initialActiveParticipant
)
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// Example: update remote surfaces when the call participants change
participantsFlow.onEach { newParticipants ->
participantExtension.updateParticipants(newParticipants)
}.launchIn(this)
}
}
Помимо уведомления удаленных поверхностей о том, какие участники присутствуют на встрече, активного участника также можно обновить с помощью ParticipantExtension#updateActiveParticipant.
Также поддерживаются необязательные действия, связанные с участниками звонка.
Приложение может использовать ParticipantExtension#addRaiseHandSupport, чтобы поддерживать функцию поднятия руки во время звонка и показывать, кто ещё поднял руку.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Notifies Jetpack that this app supports the participant
// extension and provides the initial list of participants in the call.
val participantExtension = addParticipantExtension(initialParticipants)
// Notifies Jetpack that this app supports the notion of participants
// being able to raise and lower their hands.
val raiseHandState = participantExtension.addRaiseHandSupport(
initialRaisedHands
) { onHandRaisedStateChanged ->
// handle this user's raised hand state changed updates from
// remote surfaces.
}
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// Example: update remote surfaces when the call participants change
participantsFlow.onEach { newParticipants ->
participantExtension.updateParticipants(newParticipants)
}.launchIn(this)
// notify remote surfaces of which of the participants have their
// hands raised
raisedHandsFlow.onEach { newRaisedHands ->
raiseHandState.updateRaisedHands(newRaisedHands)
}.launchIn(this)
}
}
Отключение звука во время звонка в службу поддержки
Функция отключения звука звонка позволяет пользователю отключить исходящий звук звонка без физического отключения микрофона устройства. Эта функция управляется для каждого звонка отдельно, поэтому Jetpack обрабатывает сложность управления глобальным состоянием отключения звука текущих звонков по мобильной сети, когда активен звонок VoIP. Это позволяет избежать ошибок при отключении исходящего аудио в сценариях с многократными вызовами, а также реализовать полезные функции, например индикаторы "Вы говорите?", когда пользователь говорит, не понимая, что отключил звук.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Add support for locally silencing the call's outgoing audio and
// register a handler for when the user changes the call silence state
// from a remote surface.
val callSilenceExtension = addLocalCallSilenceExtension(
initialCallSilenceState = false
) { newCallSilenceStateRequest ->
// handle the user's request to enable/disable call silence from
// a remote surface
}
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// When the call's call silence state changes, update remote
// surfaces of the new state.
callSilenceState.onEach { isSilenced ->
callSilenceExtension.updateIsLocallySilenced(isSilenced)
}.launchIn(this)
}
}
Значки звонков в службу поддержки
Значок звонка позволяет приложению задать пользовательский значок, который будет показываться на удаленных устройствах во время звонка. Этот значок может меняться во время звонка.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Add support for a custom call icon to be displayed during the
// lifetime of the call.
val callIconExtension = addCallIconExtension(
initialCallIconUri = initialUri
)
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// When the call's icon changes, update remote surfaces by providing
// the new URI.
callIconUri.onEach { newIconUri ->
callIconExtension.updateCallIconUri(newIconUri)
}.launchIn(this)
}
}
Добавлять в системный журнал звонков
Вы можете добавить звонки VoIP из своего приложения в системный журнал вызовов, чтобы они отображались в системном приложении для набора номера и пользователи могли перезванивать оттуда. Подробнее об объединенном журнале звонков…