欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

开发通话应用

借助通话应用,用户可以在自己的设备上接听或拨打语音或视频通话。通话应用使用自己的界面(而不是默认的“电话”应用界面)显示通话,如以下屏幕截图所示。

通话应用示例
使用自己界面的通话应用示例

Android 框架包含 android.telecom 软件包,其中包含可帮助您根据 Telecom 框架开发通话应用的类。根据 Telecom 框架开发应用具有以下优势:

  • 您的应用可以与设备中的原生 Telecom 子系统正常进行互操作。
  • 您的应用可以与其他同样遵循该框架的通话应用正常进行互操作。
  • 该框架有助于您的应用管理音频和视频转接。
  • 该框架有助于您的应用确定其通话是否具有焦点。

清单声明和权限

在您的应用清单中,声明您的应用使用 MANAGE_OWN_CALLSREAD_CALL_LOGREAD_PHONE_STATE 权限,如下例所示:

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

        <!-- Needed only if your calling app reads numbers from the `PHONE_STATE`
             intent action. -->
        <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
        …
    </manifest>
    

如需详细了解如何声明应用权限,请参阅权限

您必须声明一项服务,该服务指定用于在您的应用中实现 ConnectionService 类的类。Telecom 子系统要求该服务声明 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>
    

如需详细了解如何声明应用组件(包括服务),请参阅应用组件

实现连接服务

您的通话应用必须提供 Telecom 子系统能够与之绑定的 ConnectionService 类的实现。您的 ConnectionService 实现应替换以下方法:

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

Telecom 子系统调用此方法来响应您的应用对 placeCall(Uri, Bundle) 的调用,以创建新的去电。您的应用会返回 Connection 类实现的新实例(如需了解详情,请参阅实现连接)以表示新的去电。您可以通过执行以下操作来进一步自定义去电连接:

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Telecom 子系统会在您的应用调用 placeCall(Uri, Bundle) 方法且无法拨出电话时调用此方法。为了应对这种情况,您的应用应告知用户(例如,使用提醒框或消息框)无法拨出电话。如果有正在进行的紧急呼叫或者如果在您拨打电话之前另一个应用中正在进行的通话无法置于保持状态,则您的应用可能无法拨打电话。

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

Telecom 子系统会在您的应用调用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法时调用此方法,以告知系统您的应用中有新的来电。您的应用会返回 Connection 实现的新实例(如需了解详情,请参阅实现连接)以表示新的来电。您可以通过执行以下操作来进一步自定义来电连接:

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

Telecom 子系统会在您的应用调用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法时调用此方法,以告知 Telecom 有新的来电,但这通来电不受允许(如需了解详情,请参阅通话限制)。您的应用应拒绝来电且不发出提示音,可以选择发布通知以告知用户有未接电话。

实现连接

您的应用应创建 Connection 的子类来表示应用中的来电。您应在实现中替换以下方法:

onShowIncomingCallUi()

Telecom 子系统会在您添加新的来电时调用此方法,并且您的应用应显示其来电界面。

onCallAudioStateChanged(CallAudioState)

Telecom 子系统调用此方法来告知您的应用当前音频路径或模式已发生更改。这样做是为了响应您的应用使用 setAudioRoute(int) 方法对音频模式做出的更改。如果系统更改了音频路径(例如,蓝牙耳机断开连接时),也可以调用此方法。

onHold()

当 Telecom 子系统想要将通话置于保持状态时,就会调用此方法。为了响应此请求,您的应用应保持通话,然后调用 setOnHold() 方法来告知系统通话正置于保持状态。Telecom 子系统可能会在显示通话的通话服务(例如 Android Auto)希望中继用户将通话置于保持状态的请求时调用此方法。如果用户在另一个应用中进行通话,Telecom 子系统也会调用此方法。如需详细了解通话服务,请参阅 InCallService

onUnhold()

当 Telecom 子系统想要恢复已置于保持状态的通话时,就会调用此方法。您的应用恢复通话后,应调用 setActive() 方法来告知系统该通话已不再置于保持状态。当显示通话的通话服务(例如 Android Auto)想中继恢复通话的请求时,Telecom 子系统可能会调用此方法。如需详细了解通话服务,请参阅 InCallService

onAnswer()

Telecom 子系统调用此方法来告知您的应用应该接听来电。当应用接听来电后,应调用 setActive() 方法来告知系统已接听来电。如果您的应用添加了新的来电,并且另一个应用中已经有一个无法置于保持状态的正在进行的通话,则 Telecom 子系统可能会调用此方法。在这些情况下,Telecom 子系统会代表您的应用显示来电界面。该框架提供了一种过载方法,支持指定接听来电的视频状态。如需了解详情,请参阅 onAnswer(int)

onReject()

Telecom 子系统会在要拒接来电时调用此方法。您的应用拒接来电后,应调用 setDisconnected(DisconnectCause) 并指定 REJECTED 作为参数。然后,您的应用应调用 destroy() 方法来告知系统应用已处理完来电。当用户拒接了来自您应用的来电时,Telecom 子系统会调用此方法。

onDisconnect()

Telecom 子系统会在它要断开通话连接时调用此方法。通话结束后,您的应用应调用 setDisconnected(DisconnectCause) 方法并将 LOCAL 指定为参数,以表示用户请求导致通话断开连接。然后,您的应用应调用 destroy() 方法来告知 Telecom 子系统应用已处理完来电。当用户通过其他通话服务(例如 Android Auto)断开通话连接时,系统可能会调用此方法。当您必须断开通话连接以允许接听其他来电时(例如,如果用户想要进行紧急呼叫),系统也会调用此方法。如需详细了解通话服务,请参阅 InCallService

处理常见的通话场景

在调用流程中使用 ConnectionService API 涉及与 android.telecom 软件包中的其他类进行互动。以下部分介绍了常见的通话场景以及应用应如何使用 API 来处理这些场景。

接听来电

处理来电的流程会根据其他应用中是否有来电而发生变化。流程之所以会出现差异,是因为当其他应用中有正在进行的通话时,Telecom 框架必须满足一些限制条件,以确保设备上的所有通话应用都能有一个稳定的环境。如需了解详情,请参阅通话限制

其他应用中没有正在进行的通话

要在其他应用中没有正在进行的通话时接听来电,请按以下步骤操作:

  1. 您的应用会根据常规机制接收新来电。
  2. 使用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法在有新来电时告知 Telecom 子系统。
  3. Telecom 子系统绑定到您应用的 ConnectionService 实现,并使用 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 方法请求表示新来电的 Connection 类的新实例。
  4. Telecom 子系统会使用 onShowIncomingCallUi() 方法告知您的应用应显示其来电界面。
  5. 您的应用会通过具有相关联的全屏 Intent 的通知来显示其来电界面。如需了解详情,请参阅 onShowIncomingCallUi()
  6. 如果用户接听来电,则调用 setActive() 方法;如果用户拒接来电,则调用 setDisconnected(DisconnectCause),以将 REJECTED 指定为参数,然后调用 destroy() 方法。

其他应用中无法置于保持状态的正在进行的通话

要在其他应用中有无法置于保持状态的正在进行的通话时接听来电,请按以下步骤操作:

  1. 您的应用会根据常规机制接收新来电。
  2. 使用 addNewIncomingCall(PhoneAccountHandle, Bundle) 方法在有新来电时告知 Telecom 子系统。
  3. Telecom 子系统绑定到您应用的 ConnectionService 实现,并使用 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 方法请求表示新来电的 Connection 对象的新实例。
  4. Telecom 子系统会为您的来电显示来电界面。
  5. 如果用户接听该来电,则 Telecom 子系统会调用 onAnswer() 方法。您应调用 setActive() 方法,以告知 Telecom 子系统来电现已连接。
  6. 如果用户拒接该来电,则 Telecom 子系统会调用 onReject() 方法。您应调用 setDisconnected(DisconnectCause) 方法,以将 REJECTED 指定为参数,然后调用 destroy() 方法。

拨出电话

拨出电话的流程涉及处理以下情况:由于 Telecom 框架施加的限制而导致可能无法拨打电话。如需了解详情,请参阅通话限制

要拨出电话,请按以下步骤操作:

  1. 用户在您的应用内发起去电。
  2. 使用 placeCall(Uri, Bundle) 方法告知 Telecom 子系统要新拨出电话。考虑以下关于方法参数的注意事项:
    • Uri 参数表示拨出电话的地址。对于普通电话号码,请使用 tel: URI 架构。
    • 通过 Bundle 参数,您可以将应用的 PhoneAccountHandle 对象添加到 EXTRA_PHONE_ACCOUNT_HANDLE extra,以提供有关通话应用的信息。您的应用必须为每个去电提供 PhoneAccountHandle 对象。
    • 通过 Bundle 参数,您可以在 EXTRA_START_CALL_WITH_VIDEO_STATE extra 中指定 STATE_BIDIRECTIONAL 值,从而指定去电是否包含视频。请注意,默认情况下,Telecom 子系统会将视频通话转接到扬声器。
  3. Telecom 子系统绑定到您应用的 ConnectionService 实现。
  4. 如果您的应用无法拨出电话,则 Telecom 子系统会调用 onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) 方法来告知您的应用目前无法拨打电话。您的应用应告知用户无法拨出电话。
  5. 如果您的应用能够拨出电话,则 Telecom 子系统会调用 onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) 方法。您的应用应返回 Connection 类的实例来表示新去电。如需详细了解应在连接中设置的属性,请参阅实现连接服务
  6. 去电连接后,调用 setActive() 方法,以告知 Telecom 子系统通话正在进行中。

结束通话

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

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

通话限制

为了确保为用户提供一致且简单的通话体验,Telecom 框架针对管理设备上的通话施加了一些限制。例如,假设用户安装了两个通话应用,并且这两个应用实现了自行管理的 ConnectionService API:FooTalk 和 BarTalk。在这种情况下,应遵循以下限制条件:

  • 在搭载 API 级别 27 或更低版本的设备上,任何时候都只有一个应用可以保持当前通话。这种限制意味着,当用户使用 FooTalk 应用进行通话时,BarTalk 应用无法发起或接收新的通话。

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

  • 如果用户正在进行常规的受管理通话(例如,使用内置的电话或拨号器应用),则用户无法进行来自通话应用的通话。也就是说,如果用户使用移动运营商正常通话,则无法同时进行 FooTalk 或 BarTalk 通话。

  • 如果用户拨打紧急呼叫,则 Telecom 子系统会断开应用的通话连接。

  • 当用户进行紧急呼叫时,您的应用无法接听或拨出电话。

  • 如果在应用收到来电时,另一个通话应用中有正在进行的通话,则接听来电会结束另一个应用中的当前通话。您的应用不应显示其正常的来电界面。Telecom 框架会显示来电界面,并告知用户接听新来电会结束当前通话。也就是说,如果用户正在进行 FooTalk 通话,而 BarTalk 应用收到来电,则 Telecom 框架会告知用户有新的 BarTalk 来电,并且接听 BarTalk 通话会结束 FooTalk 通话。