Телеком

Новая библиотека Android Telecom Jetpack позволяет легко сообщить платформе, в каком состоянии находится ваш вызов. Исходный код и пример приложения можно найти на GitHub .

Зависимости и разрешения

Сначала откройте файл build.gradle модуля приложения и добавьте зависимость для модуля androidx Telecom:

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0-alpha02")
}

В манифесте вашего приложения объявите, что ваше приложение использует разрешение MANAGE_OWN_CALLS :

<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

Зарегистрируйте приложение

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

 private val callsManager = CallsManager(context)

var capabilities: @CallsManager.Companion.Capability Int =
    CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING

callsManager.registerAppWithTelecom(capabilities)

Интеграция платформы

Двумя наиболее распространенными сценариями вызовов для любого вызывающего приложения являются входящие и исходящие вызовы. Чтобы правильно зарегистрировать направление звонка и соответствующим образом уведомить пользователя уведомлениями, используйте API ниже.

Зарегистрировать звонок

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

companion object {
  const val APP_SCHEME = "MyCustomScheme"
  const val ALL_CALL_CAPABILITIES = (CallAttributes.SUPPORTS_SET_INACTIVE
    or CallAttributes.SUPPORTS_STREAM or CallAttributes.SUPPORTS_TRANSFER)

  const val INCOMING_NAME = "Luke"
  val INCOMING_URI: Uri = Uri.fromParts(APP_SCHEME, "", "")
  // Define all possible properties for CallAttributes
  val INCOMING_CALL_ATTRIBUTES =
    CallAttributes(
      INCOMING_NAME,
      INCOMING_URI,
      DIRECTION_INCOMING,
      CALL_TYPE_VIDEO_CALL,
      ALL_CALL_CAPABILITIES)
}

Объект callAttributes может иметь следующие свойства:

  • displayName : имя вызывающего абонента, собрания или сеанса.
  • address : Адрес вызова. Обратите внимание: это можно распространить на ссылку на собрание.
  • direction : направление вызова, например входящий или исходящий .
  • callType : информация, относящаяся к передаваемым данным, например видео и аудио.
  • callCapabilities : объект, определяющий возможности вызова.

Объект callCapabilities может иметь следующие свойства:

  • streaming : указывает, поддерживает ли вызов потоковую передачу звука на другое устройство под управлением Android.
  • transfer : Указывает, можно ли перевести вызов.
  • hold : указывает, можно ли поставить вызов на удержание.

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

Метод addCall() возвращает исключение, если устройство не поддерживает связь или если при настройке звонка произошла ошибка.

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onIsCallAnswered, // Watch needs to know if it can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
        callControlScope = this
    }
}

Ответить на звонок

После совершения входящего вызова вы должны ответить или отклонить вызов. В этом примере показано, как ответить на звонок:

when (answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {

    }
}

Если выполняется другой вызов, answer() вернет CallControlResult.Error , сообщающую, почему на вызов невозможно ответить. В этом случае пользователю необходимо поставить другой вызов на удержание.

Отклонить вызов

Чтобы отклонить вызов, отключите его с помощью DisconnectCause.Rejected .

fun onRejectCall(){
    coroutineScope.launch {
        callControlScope?.let {
            it.disconnect(DisconnectCause(DisconnectCause.REJECTED))
        }
    }
}

Исходящий звонок

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

when (setActive()) {
    is CallControlResult.Success -> {
        onIsCallActive()
    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Поставить вызов на удержание

Если ваше приложение для вызовов поддерживает удержание вызовов, используйте setInActive чтобы сообщить платформе, что ваш вызов не активен, а микрофон и камера могут использоваться другими приложениями:

when (setInActive()) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Отключить

Чтобы отключить вызов, сообщите телекоммуникационному стеку о необходимости отключения, указав действительную причину:

coroutineScope.launch {
    callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL))
}

Направить аудио

Во время разговора пользователи иногда переключаются между устройствами, например динамиком, наушником или устройством Bluetooth. Используйте API availableEndpoints и currentCallEndpoint , чтобы получить список всех устройств, доступных пользователю, и какое устройство активно.

В этом примере оба потока объединяются для создания объекта пользовательского интерфейса, который показывает пользователю список устройств и то, какое из них активно:

availableEndpoint = combine(callControlScope.availableEndpoints,
    callControlScope.currentCallEndpoint) {
    availableDevices: List<CallEndpoint>, activeDevice : CallEndpoint ->
    availableDevices.map {
        EndPointUI(
            isActive = activeDevice.endpointName == it.endpointName, it
        )
    }
}

Чтобы изменить активное устройство, используйте requestEndpointChange с CallEndpoint на который вы хотите перейти.

coroutineScope.launch {
     callControlScope?.requestEndpointChange(callEndpoint)
}

Поддержка переднего плана

Библиотека Telecom поставляется с приоритетной поддержкой. Эта библиотека использует ConnectionService для устройств под управлением Android 13 и более ранних версий. В Android 14 и более поздних версиях для правильной поддержки служб переднего плана используются микрофон и камера переднего плана . Узнайте больше о службах переднего плана .

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

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

is TelecomCall.Registered -> {
    val notification = createNotification(call)
    notificationManager.notify(TELECOM_NOTIFICATION_ID, notification)
}

Поверхностная поддержка

Часы имеют общее приложение-приемник конечной точки. Это приложение предоставляет пользователю базовый интерфейс, такой как ответ, отклонение и разъединение вызовов. Приложение поддерживает эти действия, реализуя лямбда-функции, которые сообщают платформе, что вы выполнили действие на устройстве.

Время ожидания каждой лямбда-функции истекает через 5 секунд при неудачной транзакции, если ваше приложение не отвечает.

callsManager.addCall(
        attributes,
        onIsCallAnswered, // Watch/Auto need to know if they can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
//Call Scope
}
/**
  *  Can the call be successfully answered??
  *  TIP: Check the connection/call state to see if you can answer a call
  *  Example you may need to wait for another call to hold.
  **/
val onIsCallAnswered: suspend(type: Int) -> Unit = {}

/**
  * Can the call perform a disconnect
  */
val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {}

/**
  *  Check is see if you can make the call active.
  *  Other calls and state might stop us from activating the call
  */
val onIsCallActive: suspend () -> Unit = {
    updateCurrentCall {
    }
}

/**
  * Check to see if you can make the call inactivate
  */
val onIsCallInactive: suspend () -> Unit = {}