构建通话应用

通话应用允许用户在其设备上接收或发起音频或视频通话。通话应用使用其自己的用户界面进行通话,而不是使用默认的电话应用界面,如下面的屏幕截图所示。

An example of a calling app
使用其自身用户界面的通话应用示例

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 类实现(有关更多信息,请参阅 实现连接)的新实例以表示新的呼出通话。您可以通过执行以下操作进一步自定义呼出连接

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

当您的应用调用 placeCall(Uri, Bundle) 方法且无法发起呼叫时,电信子系统会调用此方法。针对这种情况,您的应用应告知用户(例如,使用警报框或吐司)无法发起呼叫。如果存在正在进行的紧急呼叫,或者如果存在另一个应用中的正在进行的呼叫且在发起您的呼叫之前无法将其置于保持状态,则您的应用可能无法发起呼叫。

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

当您的应用调用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法来通知系统您的应用中有一个新的来电时,电信子系统会调用此方法。您的应用返回 Connection 实现的新实例(有关更多信息,请参阅 实现连接)来表示新的来电。您可以通过执行以下操作进一步自定义来电连接

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

当您的应用调用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法来通知电信子系统有新的来电,但来电未被允许时(有关更多信息,请参阅 呼叫约束),电信子系统会调用此方法。您的应用应静默拒绝来电,可以选择发布通知以告知用户错过的呼叫。

实现连接

您的应用应创建 Connection 的子类来表示应用中的呼叫。您应该在您的实现中重写以下方法

onShowIncomingCallUi()

当您添加新的来电时,电信子系统会调用此方法,并且您的应用应显示其来电 UI。

onCallAudioStateChanged(CallAudioState)

电信子系统调用此方法以告知您的应用当前音频路由或模式已更改。这是响应您的应用使用 setAudioRoute(int) 方法更改音频模式而调用的。如果系统更改了音频路由(例如,当蓝牙耳机断开连接时),也可能会调用此方法。

onHold()

当电信子系统想要将呼叫置于保持状态时,会调用此方法。响应此请求,您的应用应将呼叫置于保持状态,然后调用 setOnHold() 方法以告知系统呼叫正在保持。当显示您的呼叫的通话服务(例如 Android Auto)想要转发用户的保持呼叫请求时,电信子系统可能会调用此方法。如果用户在另一个应用中激活呼叫,电信子系统也会调用此方法。有关通话服务的更多信息,请参阅 InCallService

onUnhold()

当电信子系统想要恢复已置于保持状态的呼叫时,会调用此方法。您的应用恢复呼叫后,应调用 setActive() 方法以告知系统呼叫不再处于保持状态。当显示您的呼叫的通话服务(例如 Android Auto)想要转发恢复呼叫的请求时,电信子系统可能会调用此方法。有关通话服务的更多信息,请参阅 InCallService

onAnswer()

电信子系统调用此方法以告知您的应用应接听来电。您的应用接听呼叫后,应调用 setActive() 方法以告知系统呼叫已接听。当您的应用添加新的来电且另一个应用中已存在正在进行的呼叫且无法将其置于保持状态时,电信子系统可能会调用此方法。电信子系统在这些情况下代表您的应用显示来电 UI。框架提供了一个重载方法,该方法支持指定接听呼叫的视频状态。有关更多信息,请参阅 onAnswer(int)

onReject()

当电信子系统想要拒绝来电时,会调用此方法。您的应用拒绝呼叫后,应调用 setDisconnected(DisconnectCause) 并将 REJECTED 指定为参数。然后,您的应用应调用 destroy() 方法以告知系统应用已处理呼叫。当用户拒绝来自您的应用的来电时,电信子系统会调用此方法。

onDisconnect()

当电信子系统想要断开呼叫时,会调用此方法。呼叫结束时,您的应用应调用 setDisconnected(DisconnectCause) 方法并将 LOCAL 指定为参数,以指示用户请求导致呼叫断开。然后,您的应用应调用 destroy() 方法以告知电信子系统应用已处理呼叫。当用户通过另一个通话服务(例如 Android Auto)断开呼叫时,系统可能会调用此方法。例如,如果用户想要发起紧急呼叫,则系统也会在必须断开您的呼叫以允许发起其他呼叫时调用此方法。有关通话服务的更多信息,请参阅 InCallService

处理常见的呼叫场景

在您的呼叫流程中使用 ConnectionService API 涉及与 android.telecom 包中的其他类进行交互。以下部分描述了常见的呼叫场景以及您的应用应如何使用 API 来处理它们。

接听来电

处理来电的流程会根据其他应用中是否存在呼叫而有所不同。流程差异的原因是,当其他应用中存在活动呼叫时,电信框架必须建立一些约束,以确保设备上所有呼叫应用的稳定环境。有关更多信息,请参阅 呼叫约束

其他应用中没有活动呼叫

要在其他应用中没有活动呼叫时接听来电,请按照以下步骤操作

  1. 您的应用使用其通常的机制接收新的来电。
  2. 使用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法告知电信子系统新的来电。
  3. 电信子系统绑定到您的应用的 ConnectionService 实现并请求 Connection 类的新实例,使用 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 方法表示新的来电。
  4. 电信子系统告知您的应用应使用 onShowIncomingCallUi() 方法显示其来电用户界面。
  5. 您的应用使用带有关联的全屏意图的通知显示其来电 UI。有关更多信息,请参阅 onShowIncomingCallUi()
  6. 如果用户接受来电,则调用 setActive() 方法,或者如果用户拒绝来电,则调用 setDisconnected(DisconnectCause) 并将 REJECTED 指定为参数,然后调用 destroy() 方法。

其他应用中无法置于保持状态的活动呼叫

要在其他应用中存在无法置于保持状态的活动呼叫时接听来电,请按照以下步骤操作

  1. 您的应用使用其通常的机制接收新的来电。
  2. 使用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法告知电信子系统新的来电。
  3. 电信子系统绑定到您的应用的 ConnectionService 实现并请求 Connection 对象的新实例,使用 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 方法表示新的来电。
  4. 电信子系统显示您的来电的来电 UI。
  5. 如果用户接受呼叫,则电信子系统会调用 onAnswer() 方法。您应该调用 setActive() 方法以指示电信子系统呼叫现在已连接。

  6. 如果用户拒绝通话,电信子系统会调用 onReject() 方法。您应该调用 setDisconnected(DisconnectCause) 方法,并指定 REJECTED 作为参数,然后调用 destroy() 方法。

发起呼出电话

发起呼出电话的流程涉及处理由于电信框架施加的限制而无法发起呼出的可能性。有关更多信息,请参阅呼叫限制

要发起呼出电话,请按照以下步骤操作

  1. 用户在您的应用中发起呼出电话。
  2. 使用 placeCall(Uri, Bundle) 方法通知电信子系统新的呼出电话。请考虑方法参数的以下事项
    • Uri 参数表示呼叫目标地址。对于常规电话号码,请使用 tel: URI 方案。
    • Bundle 参数允许您通过将应用的 PhoneAccountHandle 对象添加到 EXTRA_PHONE_ACCOUNT_HANDLE 附加信息中来提供有关您的呼叫应用的信息。您的应用必须为每个呼出电话提供 PhoneAccountHandle 对象。
    • Bundle 参数还允许您通过在 EXTRA_START_CALL_WITH_VIDEO_STATE 附加信息中指定 STATE_BIDIRECTIONAL 值来指定呼出电话是否包含视频。请注意,默认情况下,电信子系统会将视频通话路由到扬声器。
  3. 电信子系统会绑定到您的应用的 ConnectionService 实现。
  4. 如果您的应用无法发起呼出电话,电信子系统会调用 onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) 方法通知您的应用当前无法发起呼叫。您的应用应通知用户无法发起呼叫。
  5. 如果您的应用能够发起呼出电话,电信子系统会调用 onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) 方法。您的应用应返回 Connection 类的实例以表示新的呼出电话。有关应在连接中设置的属性的更多信息,请参阅实现连接服务
  6. 呼出电话接通后,请调用 setActive() 方法通知电信子系统通话处于活动状态。

结束通话

要结束通话,请按照以下步骤操作

  1. 如果用户终止了通话,则调用 setDisconnected(DisconnectCause) 并发送 LOCAL 作为参数;如果对方终止了通话,则发送 REMOTE 作为参数。
  2. 调用 destroy() 方法。

呼叫限制

为了确保用户获得一致且简单的通话体验,电信框架对设备上的通话管理施加了一些限制。例如,假设用户安装了两个实现了自管理 ConnectionService API 的呼叫应用,FooTalk 和 BarTalk。在这种情况下,适用以下限制

  • 在运行 API 级别 27 或更低版本的设备上,一次只能有一个应用维持正在进行的通话。此限制意味着,当用户使用 FooTalk 应用进行通话时,BarTalk 应用无法发起或接听新的通话。

    在运行 API 级别 28 或更高版本的设备上,如果 FooTalk 和 BarTalk 都声明了 CAPABILITY_SUPPORT_HOLDCAPABILITY_HOLD 权限,则用户可以通过在应用之间切换来发起或接听另一个通话,从而维持多个正在进行的通话。

  • 如果用户正在进行常规管理的通话(例如,使用内置的电话或拨号器应用),则用户无法参与来自呼叫应用发起的通话。这意味着,如果用户正在使用其移动运营商进行常规通话,则他们无法同时进行 FooTalk 或 BarTalk 通话。

  • 如果用户拨打紧急电话,电信子系统会断开您的应用的通话。

  • 在用户进行紧急电话时,您的应用无法接收或发起通话。

  • 如果另一个呼叫应用正在进行通话,而您的应用收到来电,则接到来电会结束另一个应用中的所有正在进行的通话。您的应用不应显示其通常的来电用户界面。电信框架会显示来电用户界面,并通知用户接听新通话将结束其正在进行的通话。这意味着,如果用户正在进行 FooTalk 通话,而 BarTalk 应用收到来电,则电信框架会通知用户他们有一个新的 BarTalk 来电,并且接听 BarTalk 来电将结束其 FooTalk 通话。