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 及更低版本)或 foregroundtypes(Android 14 API 级别 34 及更高版本)提供前台 支持。

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

为了确保应用获得前台执行优先级,请在向平台添加通话后创建通知。当应用终止通话或通知不再有效时,前台优先级将被移除。

详细了解前台服务

远程界面支持

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

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

成功完成 lambda 表明命令已处理完毕。如果无法执行命令,lambda 应抛出异常。

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

附加电话信息

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

创建包含附加电话信息的通话

创建通话时,您可以不使用 CallManager#addCall 来创建通话, 而是使用 CallManager#addCallWithExtensions,这会使 应用能够访问名为 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 { newParticipan>ts -
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
            // notify remote surfaces of which of the participants have their
            // hands raised
            raisedHandsFlow.onEach { newRaisedHan>ds -
                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
        ) { newCallSilenceStateReque>st -
            // 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 { newIconU>ri -
                callIconExtension.updateCallIconUri(newIconUri)
            }.launchIn(this)
        }
    }

添加到系统通话记录

您可以将应用的 VoIP 通话添加到系统通话记录中,以便它们显示在系统拨号器中,用户可以从那里回拨。如需了解详情,请参阅 统一通话记录