Кор-Телеком

Библиотека Core-Telecom упрощает процесс интеграции вашего приложения для звонков с платформой Android, предоставляя надежный и согласованный набор API.

Если вы хотите изучить практические реализации, вы можете найти примеры приложений на GitHub:

  • Lightweight Sample App — Минимальный пример, демонстрирующий использование Core-Telecom API. Идеально подходит для быстрого понимания фундаментальных концепций.
  • Comprehensive Sample App (разработано командой Core-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)

Управление вызовами

Используйте API Core-Telecom для создания и управления жизненным циклом вызова.

Создать звонок

Объект CallAttributesCompat определяет свойства уникального вызова, который может иметь следующие характеристики:

  • displayName : имя вызывающего абонента.
  • address : адрес вызова (например, номер телефона, ссылка на встречу).
  • direction : входящее или исходящее.
  • callType : Аудио или видео.
  • callCapabilities : Поддерживает передачу и удержание вызова.

Вот пример того, как создать входящий звонок:

fun createIncomingCallAttributes(
    callerName: String,
    callerNumber: String,
    isVideoCall: Boolean): CallAttributesCompat {
    val addressUri = Uri.parse("YourAppScheme:$callerNumber")

    // Define capabilities supported by your call.
    val callCapabilities = CallAttributesCompat.CallCapability(
        supportsSetInactive = CallAttributesCompat.SUPPORTS_SET_INACTIVE // Call can be made inactive (implies hold)
    )

    return CallAttributesCompat(
        displayName = callerName,
        address = addressUri,
        direction = CallAttributesCompat.DIRECTION_INCOMING,
        callType = if (isVideoCall) CallAttributesCompat.CALL_TYPE_VIDEO_CALL else CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
        callCapabilitiesCompat = callCapabilities
    )
}

Добавить звонок

Используйте 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

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 и ниже) или foregroundtypes (Android 14 API уровня 34 и выше) для поддержки переднего плана.

В рамках требований к работе в активном режиме приложение должно публиковать уведомление, чтобы пользователи знали, что приложение работает в активном режиме.

Чтобы гарантировать, что ваше приложение получит приоритет выполнения переднего плана, создайте уведомление после добавления вызова с платформой. Приоритет переднего плана удаляется, когда ваше приложение завершает вызов или ваше уведомление больше недействительно.

Узнайте больше о приоритетных службах .

Удаленная поддержка Surface

Удаленные устройства (умные часы, гарнитуры Bluetooth, Android Auto) способны управлять вызовами без прямого взаимодействия с телефоном. Ваше приложение должно реализовать лямбда-выражения обратного вызова ( onAnswerCall , onSetCallDisconnected , onSetCallActive , onSetCallInactive ), предоставленные CallsManager.addCall для обработки действий, инициированных этими устройствами.

При выполнении удаленного действия вызывается соответствующая лямбда-функция.

Успешное завершение лямбды сигнализирует о том, что команда была обработана. Если команда не может быть выполнена, лямбда должна выдать исключение.

Правильная реализация обеспечивает бесперебойное управление вызовами на разных устройствах. Тщательно протестируйте с различными удаленными поверхностями.

Расширения вызовов

Помимо управления состоянием вызова и аудиомаршрутом ваших вызовов, библиотека также поддерживает расширения вызовов, которые являются дополнительными функциями, которые ваше приложение может реализовать для более богатого опыта вызовов на удаленных поверхностях, таких как Android Auto. Эти функции включают в себя конференц-залы, отключение вызова и дополнительные значки вызовов. Когда ваше приложение реализует расширение, информация, предоставляемая приложением, будет синхронизирована со всеми подключенными устройствами, которые также поддерживают отображение этих расширений в своем пользовательском интерфейсе. Это означает, что эти функции также будут доступны на удаленных устройствах для взаимодействия пользователей.

Создать звонок с расширениями

При создании вызова вместо использования CallManager#addCall для создания вызова вы можете использовать CallManager#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)
        }
    }