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 和回调,将新呼叫添加到系统并管理远程界面更新。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 中,使用 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 及更低)或 前台类型(Android 14 API 级别 34 及更高)来实现前台支持。

作为前台要求的一部分,应用必须发布通知,以便用户知道应用正在前台运行。

为确保您的应用获得前台执行优先级,请在通过平台添加呼叫后立即创建通知。当您的应用终止呼叫或通知不再有效时,前台优先级将被移除。

了解更多关于前台服务的信息.

远程界面支持

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

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

Lambda 的成功完成表示命令已处理。如果命令无法执行,lambda 应该抛出异常。

正确的实现可确保跨不同设备进行无缝呼叫控制。请使用各种远程界面进行全面测试。

呼叫扩展

除了管理呼叫的状态和音频路由之外,该库还支持呼叫扩展,这些是您的应用可以实现的附加功能,以在远程界面(如 Android Auto)上获得更丰富的呼叫体验。这些功能包括会议室、呼叫静音和其他呼叫图标。当您的应用实现扩展时,应用提供的信息将与所有支持在其 UI 中显示这些扩展的连接设备同步。这意味着这些功能在远程设备上也可用,供用户交互。

创建带扩展的呼叫

创建呼叫时,您可以使用 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)
        }
    }

除了通知远程界面呼叫中有哪些参与者之外,还可以使用 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)
        }
    }