Core-Telecom

Core-Telecom 程式庫提供一套穩健且一致的 API,可簡化將通話應用程式與 Android 平台整合的程序

如要瞭解實際的實作方式,請前往 GitHub 查看範例應用程式:

設定 Core-Telecom

在應用程式的 build.gradle 檔案中新增 androidx.core:core-telecom 依附元件:

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:通話地址 (例如電話號碼、Meet 會議連結)。
  • 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 */ }
}

拒接來電

使用 disconnect()DisconnectCause.REJECTEDCallControlScope 中拒接來電:

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

管理通話音訊端點

使用 currentCallEndpointavailableEndpointsisMuted Flow 觀察及管理 CallControlScope 內的音訊端點

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) 上提供更豐富的通話體驗。這些功能包括會議室、通話靜音和額外的通話圖示。應用程式實作擴充功能時,應用程式提供的資訊會與所有連線裝置同步,這些裝置也支援在 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)
        }
    }

支援通話靜音

通話靜音功能可讓使用者要求應用程式將通話的輸出音訊設為靜音,而不必實際將裝置麥克風設為靜音。這項功能是依據每次通話管理,因此當 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)
        }
    }

新增至系統通話記錄

您可以將應用程式的 VoIP 通話新增至系統通話記錄,讓通話記錄顯示在系統撥號程式中,方便使用者從該處回撥。詳情請參閱「整合通話記錄」。