Core-Telecom

Core-Telecom 库通过提供一组强大且一致的 API,简化了将调用应用与 Android 平台集成的过程

如果您想探索实际实现,可以前往 GitHub 查找示例应用:

设置 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")

    // Define capabilities supported by your call.
    val callCapabilities = CallAttributesCompat.CallCapability(
        supportsSetInactive = CallAttributesCompat.SUPPORTS_SET_INACTIVE // Call can be made inactive (implies hold)
    )

    return CallAttributesCompat(
        displayName = callerName,
        address = addressUri,
        direction = CallAttributesCompat.DIRECTION_INCOMING,
        callType = if (isVideoCall) CallAttributesCompat.CALL_TYPE_VIDEO_CALL else CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
        callCapabilitiesCompat = callCapabilities
    )
}

添加通话

callsManager.addCallCallAttributesCompat 和回调结合使用,可向系统添加新调用并管理远程 Surface 更新。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 中使用 DisconnectCause.REJECTEDdisconnect() 拒接来电:

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 中的 currentCallEndpointavailableEndpointsisMuted Flow 监控和管理音频端点

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)
}

前台支持

该库使用 ConnectionService(Android 13 API 级别 33 及更低版本)或 foregroundtypes(Android 14 API 级别 34 及更高版本)来提供前台支持。

根据前台运行要求,应用必须发布通知,以便用户知道应用正在前台运行。

为确保您的应用获得前台执行优先级,请在添加与平台的调用后创建通知。当您的应用终止调用或通知不再有效时,系统会移除前台优先级。

详细了解前台服务

远程 Surface 支持

远程设备(智能手表、蓝牙耳机、Android Auto)无需直接与手机互动,即可管理通话。您的应用必须实现提供给 CallsManager.addCall 的回调 lambda(onAnswerCallonSetCallDisconnectedonSetCallActiveonSetCallInactive),以处理这些设备发起的操作。

当发生远程操作时,系统会调用相应的 lambda。

成功完成 lambda 会指示系统已处理该命令。如果无法执行该命令,lambda 应抛出异常。

正确的实现可确保在不同设备上无缝控制通话。使用各种远程界面进行全面测试。

附加电话信息

除了管理通话状态和音频路由之外,该库还支持通话扩展,这些扩展是可选功能,您的应用可以实现这些扩展,以便在 Android Auto 等远程平台上提供更丰富的通话体验。这些功能包括会议室、来电静音和其他来电图标。当您的应用实现扩展程序时,该应用提供的信息将与所有已连接且也支持在其界面中显示这些扩展程序的设备同步。这意味着,这些功能还将在远程设备上提供,供用户互动。

创建包含附加信息的通话

创建通话时,您可以改用 CallManager#addCallWithExtensions 来创建通话,而不是使用 CallManager#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)
        }
    }

除了通知远程 Surface 通话中的参与者之外,还可以使用 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)
        }
    }

支持来电静音

借助通话静音功能,用户可以请求应用静音通话的出站音频,而无需实际静音设备的麦克风。此功能按通话进行管理,因此在 VOIP 通话处于活跃状态时,Jetpack 会处理管理正在进行的移动网络通话的全球静音状态的复杂性。这样一来,在多通话场景中,静音发出去的音频的错误几率就会降低,同时还能提供实用功能,例如在用户在说话时没有意识到已启用通话静音功能时,显示“您在说话吗?”指示。

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)
        }
    }

支持通话图标

借助通话图标,应用可以指定一个自定义图标来表示通话,并在通话期间在远程 Surface 上显示该图标。此图标还可以在通话生命周期内更新。

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)
        }
    }