Core-Telecom 库提供了一组强大且一致的 API,可简化将通话应用与 Android 平台集成的流程
如果您想探索实际实现,可以在 GitHub 上找到示例应用:
- 轻量级示例应用 - 一个演示
Core-TelecomAPI 用法的最简示例。非常适合快速了解基本概念。 - 综合示例应用(由核心电信团队开发)- 一款功能更丰富的应用,展示了高级电信功能和最佳实践。此资源非常适合用于了解复杂的集成方案。
设置 Core-Telecom
将 androidx.core:core-telecom 依赖项添加到应用的 build.gradle 文件中:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0")
}
在 AndroidManifest.xml 中声明 MANAGE_OWN_CALLS 权限:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
注册您的应用
使用 CallsManager 向 Android 注册您的通话应用,即可开始向系统添加通话。注册时,请指定应用的功能(例如,音频、视频支持):
val callsManager = CallsManager(context)
val capabilities: @CallsManager.Companion.Capability Int =
(CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)
callsManager.registerAppWithTelecom(capabilities)
通话管理
使用 Core-Telecom API 创建和管理通话生命周期。
创建电话素材资源
CallAttributesCompat 对象定义了唯一通话的属性,该通话可以具有以下特征:
displayName:调用方名称。address:通话地址(例如,电话号码、会议链接)。direction:来电或去电。callType:音频或视频。callCapabilities:支持转接和保持。
以下示例展示了如何创建来电:
fun createIncomingCallAttributes(
callerName: String,
callerNumber: String,
isVideoCall: Boolean): CallAttributesCompat {
val addressUri = Uri.parse("YourAppScheme:$callerNumber")
return CallAttributesCompat(
displayName = callerName,
address = addressUri,
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = if (isVideoCall) {
CallAttributesCompat.CALL_TYPE_VIDEO_CALL
} else {
CallAttributesCompat.CALL_TYPE_AUDIO_CALL
},
callCapabilities = CallAttributesCompat.SUPPORTS_SET_INACTIVE
)
}
添加通话
使用 callsManager.addCall 与 CallAttributesCompat 和回调相结合,向系统添加新调用并管理远程界面更新。addCall 块中的 callControlScope 主要用于让应用转换通话状态并接收音频更新:
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 */ }
}
拒接来电
使用 CallControlScope 内的 disconnect() 和 DisconnectCause.REJECTED 拒绝来电:
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))
管理通话音频端点
使用 CallControlScope 中的 currentCallEndpoint、availableEndpoints 和 isMuted Flow 观察和管理音频端点。使用 Telecom 时,请勿使用 AudioManager#setCommunicationDevice 或 AudioManager#startBluetoothSco API 来管理音频路由;否则会导致通话中出现音频问题。
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)
}
前台支持
该库在 Android 13(API 级别 33)及更低版本上使用 ConnectionService,或在 Android 14(API 级别 34)及更高版本上使用前台服务类型,以提供前台支持。
为了在应用处于后台状态时保持通话有效,请在 Service 前台服务(例如 LifecycleService)内托管 CallsManager,并在 AndroidManifest.xml 中声明 phoneCall 前台服务类型:
<service
android:name=".TelecomVoipService"
android:foregroundServiceType="phoneCall" />
根据前台要求,您的应用必须发布 NotificationCompat.CallStyle 通知,以便用户知道前台有通话正在进行。为确保应用获得前台执行优先级,请在添加与平台的调用后,使用 startForeground 将服务提升为前台服务:
startForeground(
notificationId,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
)
远程 Surface 支持
远程设备(智能手表、蓝牙耳机、Android Auto)无需直接与手机互动即可管理通话。您的应用必须实现提供给 CallsManager.addCall 的回调 lambda(onAnswerCall、onSetCallDisconnected、onSetCallActive、onSetCallInactive),以处理由这些设备发起的操作。
当远程操作发生时,系统会调用相应的 lambda。
如果 Lambda 成功完成,则表示命令已处理完毕。如果无法遵守该命令,lambda 应抛出异常。
正确实现可确保在不同设备上实现无缝通话控制。 使用各种远程界面进行全面测试。
附加电话信息
除了管理通话状态和音频路由之外,该库还支持通话扩展功能,这些功能是可选功能,您的应用可以实现这些功能,以便在 Android Auto 等远程界面上提供更丰富的通话体验。这些功能包括会议室、通话静音和额外的通话图标。当应用实现扩展程序时,应用提供的信息将与所有已连接的设备同步,这些设备还支持在其界面中显示这些扩展程序。这意味着,用户还可以在远程设备上使用这些功能。
使用扩展程序创建通话
创建通话时,您可以使用 CallsManager.addCallWithExtensions 而不是 CallsManager.addCall 来创建通话,这样可让应用访问名为 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)
}
}
添加到系统调用日志
您可以将应用的 VoIP 通话添加到系统通话记录中,以便这些通话显示在系统拨号器中,用户可以从那里回拨。如需了解详情,请参阅统一通话记录。