Создайте приложение для звонков

Приложение для звонков позволяет пользователям принимать или совершать аудио- или видеовызовы на своем устройстве. Приложения для вызовов используют для вызовов собственный пользовательский интерфейс вместо интерфейса приложения «Телефон» по умолчанию, как показано на следующем снимке экрана.

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

Платформа Android включает пакет android.telecom , который содержит классы, которые помогут вам создать приложение для звонков в соответствии с телекоммуникационной платформой. Создание вашего приложения в соответствии с телекоммуникационной структурой дает следующие преимущества:

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

Манифестные декларации и разрешения

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

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

Дополнительные сведения об объявлении разрешений приложения см. в разделе Разрешения .

Вы должны объявить службу, определяющую класс, реализующий класс ConnectionService в вашем приложении. Телекоммуникационная подсистема требует, чтобы служба объявила разрешение BIND_TELECOM_CONNECTION_SERVICE для возможности привязки к ней. В следующем примере показано, как объявить службу в манифесте приложения:

<service android:name="com.example.MyConnectionService"
    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.ConnectionService" />
    </intent-filter>
</service>

Дополнительные сведения об объявлении компонентов приложения, включая службы, см. в разделе Компоненты приложения .

Реализовать службу подключения

Ваше вызывающее приложение должно предоставить реализацию класса ConnectionService , к которому может привязаться телекоммуникационная подсистема. Ваша реализация ConnectionService должна переопределить следующие методы:

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

Подсистема связи вызывает этот метод в ответ на вызов вашего приложения placeCall(Uri, Bundle) для создания нового исходящего вызова. Ваше приложение возвращает новый экземпляр реализации класса Connection (дополнительную информацию см. в разделе Реализация соединения ) для представления нового исходящего вызова. Вы можете дополнительно настроить исходящее соединение, выполнив следующие действия:

  • Ваше приложение должно вызвать метод setConnectionProperties(int) с константой PROPERTY_SELF_MANAGED в качестве аргумента, чтобы указать, что соединение возникло из вызывающего приложения.
  • Если ваше приложение поддерживает удержание вызовов, вызовите метод setConnectionCapabilities(int) и задайте в качестве аргумента значение битовой маски констант CAPABILITY_HOLD и CAPABILITY_SUPPORT_HOLD .
  • Чтобы задать имя вызывающего абонента, используйте метод setCallerDisplayName(String, int) , передав константу PRESENTATION_ALLOWED в качестве параметра int , чтобы указать, что имя вызывающего абонента должно быть показано.
  • Чтобы убедиться, что исходящий вызов имеет соответствующее состояние видео, вызовите метод setVideoState(int) объекта Connection и отправьте значение, возвращаемое методом getVideoState() объекта ConnectionRequest .
onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Подсистема телекоммуникаций вызывает этот метод, когда ваше приложение вызывает метод placeCall(Uri, Bundle) и исходящий вызов не может быть выполнен. В ответ на эту ситуацию ваше приложение должно проинформировать пользователя (например, с помощью окна предупреждения или всплывающего уведомления), что исходящий вызов не может быть выполнен. Ваше приложение может не иметь возможности совершить вызов, если есть текущий вызов службы экстренной помощи или если в другом приложении есть текущий вызов, который нельзя удержать перед совершением вызова.

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

Подсистема телекоммуникаций вызывает этот метод, когда ваше приложение вызывает метод addNewIncomingCall(PhoneAccountHandle, Bundle) чтобы сообщить системе о новом входящем вызове в вашем приложении. Ваше приложение возвращает новый экземпляр реализации Connection (дополнительную информацию см. в разделе Реализация соединения ) для представления нового входящего вызова. Вы можете дополнительно настроить входящее соединение, выполнив следующие действия:

  • Ваше приложение должно вызвать метод setConnectionProperties(int) с константой PROPERTY_SELF_MANAGED в качестве аргумента, чтобы указать, что соединение возникло из вызывающего приложения.
  • Если ваше приложение поддерживает удержание вызовов, вызовите метод setConnectionCapabilities(int) и задайте в качестве аргумента значение битовой маски констант CAPABILITY_HOLD и CAPABILITY_SUPPORT_HOLD .
  • Чтобы задать имя вызывающего абонента, используйте метод setCallerDisplayName(String, int) , передав константу PRESENTATION_ALLOWED в качестве параметра int , чтобы указать, что имя вызывающего абонента должно быть показано.
  • Чтобы указать номер телефона или адрес входящего звонка, используйте метод setAddress(Uri, int) объекта Connection .
  • Чтобы убедиться, что исходящий вызов имеет соответствующее состояние видео, вызовите метод setVideoState(int) объекта Connection и отправьте значение, возвращаемое методом getVideoState() объекта ConnectionRequest .
onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Подсистема телекоммуникаций вызывает этот метод, когда ваше приложение вызывает метод addNewIncomingCall(PhoneAccountHandle, Bundle) чтобы сообщить Telecom о новом входящем вызове, но входящий вызов не разрешен (дополнительную информацию см. в разделе ограничения вызовов ). Ваше приложение должно молча отклонять входящий вызов, при необходимости публикуя уведомление, информирующее пользователя о пропущенном вызове.

Реализуйте соединение

Ваше приложение должно создать подкласс Connection для представления вызовов в вашем приложении. В вашей реализации следует переопределить следующие методы:

onShowIncomingCallUi()

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

onCallAudioStateChanged(CallAudioState)

Телекоммуникационная подсистема вызывает этот метод, чтобы сообщить вашему приложению, что текущий аудиомаршрут или режим изменились. Это вызывается в ответ на то, что ваше приложение меняет режим звука с помощью метода setAudioRoute(int) . Этот метод также можно вызвать, если система меняет маршрут аудио (например, при отключении Bluetooth-гарнитуры).

onHold()

Подсистема связи вызывает этот метод, когда хочет удержать вызов. В ответ на этот запрос ваше приложение должно удержать вызов, а затем вызвать метод setOnHold() , чтобы сообщить системе о том, что вызов удерживается. Подсистема связи может вызвать этот метод, когда служба вызова, например Android Auto, которая показывает ваш вызов, хочет передать запрос пользователя на удержание вызова. Подсистема связи также вызывает этот метод, если пользователь активирует вызов в другом приложении. Дополнительные сведения об услугах вызова см. в InCallService .

onUnhold()

Подсистема связи вызывает этот метод, когда хочет возобновить вызов, который был поставлен на удержание. Как только ваше приложение возобновит вызов, оно должно вызвать метод setActive() , чтобы сообщить системе, что вызов больше не находится на удержании. Подсистема связи может вызвать этот метод, когда служба вызова, например Android Auto, которая показывает ваш вызов, хочет передать запрос на возобновление вызова. Дополнительные сведения об услугах вызова см. в InCallService .

onAnswer()

Подсистема связи вызывает этот метод, чтобы сообщить вашему приложению, что на входящий вызов необходимо ответить. Как только ваше приложение ответит на вызов, оно должно вызвать метод setActive() , чтобы сообщить системе, что на вызов получен ответ. Подсистема связи может вызвать этот метод, когда ваше приложение добавляет новый входящий вызов, а в другом приложении уже есть текущий вызов, который нельзя поставить на удержание. В этих случаях подсистема связи отображает пользовательский интерфейс входящего вызова от имени вашего приложения. Платформа предоставляет перегруженный метод, который обеспечивает поддержку указания состояния видео, в котором можно ответить на вызов. Дополнительные сведения см. в onAnswer(int) .

onReject()

Подсистема связи вызывает этот метод, когда хочет отклонить входящий вызов. Как только ваше приложение отклонит вызов, оно должно вызвать setDisconnected(DisconnectCause) и указать REJECTED в качестве параметра. Затем ваше приложение должно вызвать метод destroy() , чтобы сообщить системе, что приложение обработало вызов. Подсистема связи вызывает этот метод, когда пользователь отклоняет входящий вызов из вашего приложения.

onDisconnect()

Подсистема связи вызывает этот метод, когда хочет отключить вызов. После завершения вызова ваше приложение должно вызвать метод setDisconnected(DisconnectCause) и указать LOCAL в качестве параметра, чтобы указать, что запрос пользователя привел к отключению вызова. Затем ваше приложение должно вызвать метод destroy() , чтобы сообщить подсистеме связи о том, что приложение обработало вызов. Система может вызвать этот метод, когда пользователь отключил вызов через другую службу вызова, например Android Auto. Система также вызывает этот метод, когда ваш вызов необходимо отключить, чтобы разрешить другой вызов, например, если пользователь хочет сделать экстренный вызов. Дополнительные сведения об услугах вызова см. в InCallService .

Обработка распространенных сценариев вызовов

Использование API ConnectionService в потоке вызовов предполагает взаимодействие с другими классами пакета android.telecom . В следующих разделах описываются распространенные сценарии вызовов и то, как ваше приложение должно использовать API для их обработки.

Отвечать на входящие звонки

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

Нет активных звонков в других приложениях

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

  1. Ваше приложение получает новый входящий вызов, используя свои обычные механизмы.
  2. Используйте метод addNewIncomingCall(PhoneAccountHandle, Bundle) чтобы сообщить подсистеме связи о новом входящем вызове.
  3. Подсистема телекоммуникаций привязывается к реализации ConnectionService вашего приложения и запрашивает новый экземпляр класса Connection , представляющий новый входящий вызов, с помощью метода onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) .
  4. Подсистема связи сообщает вашему приложению, что оно должно показать пользовательский интерфейс входящего вызова с помощью метода onShowIncomingCallUi() .
  5. Ваше приложение отображает входящий пользовательский интерфейс с помощью уведомления с соответствующим намерением полноэкранного режима. Дополнительные сведения см. в onShowIncomingCallUi() .
  6. Вызовите метод setActive() , если пользователь принимает входящий вызов, или setDisconnected(DisconnectCause) указав REJECTED в качестве параметра, за которым следует вызов метода destroy() , если пользователь отклоняет входящий вызов.

Активные вызовы в других приложениях, которые нельзя поставить на удержание

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

  1. Ваше приложение получает новый входящий вызов, используя свои обычные механизмы.
  2. Используйте метод addNewIncomingCall(PhoneAccountHandle, Bundle) чтобы сообщить подсистеме связи о новом входящем вызове.
  3. Подсистема телекоммуникаций привязывается к реализации ConnectionService вашего приложения и запрашивает новый экземпляр объекта Connection , представляющего новый входящий вызов, с помощью метода onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) .
  4. Подсистема связи отображает пользовательский интерфейс входящего вызова.
  5. Если пользователь принимает вызов, подсистема связи вызывает метод onAnswer() . Вам следует вызвать метод setActive() чтобы указать подсистеме связи, что вызов теперь подключен.
  6. Если пользователь отклоняет вызов, подсистема связи вызывает метод onReject() . Вам следует вызвать метод setDisconnected(DisconnectCause) , указав REJECTED в качестве параметра, а затем вызвать метод destroy() .

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

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

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

  1. Пользователь инициирует исходящий звонок в вашем приложении.
  2. Используйте метод placeCall(Uri, Bundle) , чтобы сообщить подсистеме связи о новом исходящем вызове. Примите во внимание следующие параметры метода:
    • Параметр Uri представляет адрес, по которому осуществляется вызов. Для обычных телефонных номеров используйте схему URI tel: :.
    • Параметр Bundle позволяет вам предоставить информацию о вашем вызывающем приложении, добавив объект PhoneAccountHandle вашего приложения в дополнительный EXTRA_PHONE_ACCOUNT_HANDLE . Ваше приложение должно предоставлять объект PhoneAccountHandle для каждого исходящего вызова.
    • Параметр Bundle также позволяет указать, включает ли исходящий вызов видео, указав значение STATE_BIDIRECTIONAL в дополнительном параметре EXTRA_START_CALL_WITH_VIDEO_STATE . Учтите, что по умолчанию подсистема связи направляет видеовызовы на громкую связь.
  3. Телекоммуникационная подсистема привязывается к реализации ConnectionService вашего приложения.
  4. Если ваше приложение не может выполнить исходящий вызов, подсистема телекоммуникаций вызывает метод onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) чтобы сообщить вашему приложению, что вызов не может быть выполнен в текущий момент. Ваше приложение должно сообщить пользователю, что звонок не может быть осуществлен.
  5. Если ваше приложение может выполнить исходящий вызов, подсистема связи вызывает метод onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) . Ваше приложение должно вернуть экземпляр класса Connection для представления нового исходящего вызова. Дополнительные сведения о свойствах, которые следует задать в соединении, см. в разделе Реализация службы подключения .
  6. Когда исходящий вызов подключен, вызовите метод setActive() , чтобы сообщить подсистеме связи о том, что вызов активен.

Завершить вызов

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

  1. Вызовите setDisconnected(DisconnectCause) , отправив LOCAL в качестве параметра, если пользователь завершил вызов, или отправьте REMOTE в качестве параметра, если другая сторона завершила вызов.
  2. Вызовите метод destroy() .

Ограничения вызова

Чтобы обеспечить единообразие и простоту звонков для ваших пользователей, телекоммуникационная платформа налагает некоторые ограничения на управление вызовами на устройстве. Например, предположим, что пользователь установил два вызывающих приложения, которые реализуют самоуправляемый API ConnectionService : FooTalk и BarTalk. В этом случае применяются следующие ограничения:

  • На устройствах, работающих на уровне API 27 или ниже, только одно приложение может поддерживать текущий вызов в любой момент времени. Это ограничение означает, что, пока у пользователя есть текущий вызов с помощью приложения FooTalk, приложение BarTalk не может инициировать или принять новый вызов.

    На устройствах, работающих на уровне API 28 или выше, если и FooTalk, и BarTalk объявляют разрешения CAPABILITY_SUPPORT_HOLD и CAPABILITY_HOLD , то пользователь может поддерживать более одного текущего вызова, переключаясь между приложениями, чтобы инициировать или ответить на другой вызов.

  • Если пользователь участвует в обычных управляемых вызовах (например, с помощью встроенного приложения «Телефон» или «Дозвонщик»), он не может участвовать в вызовах, исходящих из вызывающих приложений. Это означает, что если пользователь осуществляет обычный вызов через своего оператора мобильной связи, он не может одновременно участвовать в вызове FooTalk или BarTalk.

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

  • Ваше приложение не может принимать или совершать вызовы, пока пользователь выполняет экстренный вызов.

  • Если в другом вызывающем приложении происходит текущий вызов, когда ваше приложение получает входящий вызов, ответ на входящий вызов завершает все текущие вызовы в другом приложении. Ваше приложение не должно отображать обычный пользовательский интерфейс входящего вызова. Платформа телекоммуникаций отображает пользовательский интерфейс входящего вызова и информирует пользователя о том, что ответ на новый вызов завершит его текущий вызов(ы). Это означает, что если пользователь находится в вызове FooTalk и приложение BarTalk получает входящий вызов, телекоммуникационная платформа информирует пользователя о том, что у него есть новый входящий вызов BarTalk и что ответ на вызов BarTalk завершит его вызов FooTalk.