Core-Telecom

Thư viện Core-Telecom giúp đơn giản hoá quy trình tích hợp ứng dụng gọi của bạn với nền tảng Android bằng cách cung cấp một bộ API mạnh mẽ và nhất quán

Nếu muốn khám phá các cách triển khai thực tế, bạn có thể tìm thấy các ứng dụng mẫu trên GitHub:

Thiết lập Core-Telecom

Thêm phần phụ thuộc androidx.core:core-telecom vào tệp build.gradle của ứng dụng:

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0")
}

Khai báo quyền MANAGE_OWN_CALLS trong AndroidManifest.xml của bạn:

<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

Đăng ký ứng dụng của bạn

Đăng ký ứng dụng gọi điện của bạn với Android bằng cách sử dụng CallsManager để bắt đầu thêm các cuộc gọi vào hệ thống. Khi đăng ký, hãy chỉ định các chức năng của ứng dụng (ví dụ: hỗ trợ âm thanh, video):

val callsManager = CallsManager(context)

val capabilities: @CallsManager.Companion.Capability Int =
    (CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)

callsManager.registerAppWithTelecom(capabilities)

Quản lý cuộc gọi

Sử dụng Core-Telecom API để tạo và quản lý vòng đời cuộc gọi.

Tạo cuộc gọi

Đối tượng CallAttributesCompat xác định các thuộc tính của một cuộc gọi duy nhất, có thể có các đặc điểm sau:

  • displayName: tên người gọi.
  • address: Địa chỉ cuộc gọi (ví dụ: số điện thoại, đường liên kết đến cuộc họp).
  • direction: Cuộc gọi đến hoặc đi.
  • callType: Âm thanh hoặc video.
  • callCapabilities: Hỗ trợ chuyển và giữ cuộc gọi.

Sau đây là ví dụ về cách tạo cuộc gọi đến:

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

Thêm cuộc gọi

Sử dụng callsManager.addCall với CallAttributesCompat và lệnh gọi lại để thêm một cuộc gọi mới vào hệ thống và quản lý các bản cập nhật nền tảng từ xa. callControlScope trong khối addCall chủ yếu cho phép ứng dụng của bạn chuyển đổi trạng thái cuộc gọi và nhận thông tin cập nhật về âm thanh:

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

Trả lời cuộc gọi

Trả lời cuộc gọi đến trong vòng CallControlScope:

when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> { /* Call answered */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Từ chối cuộc gọi

Từ chối cuộc gọi bằng cách sử dụng disconnect() với DisconnectCause.REJECTED trong CallControlScope:

disconnect(DisconnectCause(DisconnectCause.REJECTED))

Thực hiện cuộc gọi đi

Đặt cuộc gọi đi ở trạng thái đang hoạt động sau khi bên từ xa trả lời:

when (val result = setActive()) {
    is CallControlResult.Success -> { /* Call active */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Chuyển cuộc gọi sang chế độ giữ máy

Sử dụng setInactive() để giữ cuộc gọi:

when (val result = setInactive()) {
    is CallControlResult.Success -> { /* Call on hold */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Ngắt kết nối cuộc gọi

Ngắt kết nối cuộc gọi bằng disconnect() với DisconnectCause:

disconnect(DisconnectCause(DisconnectCause.LOCAL))

Quản lý các thiết bị đầu cuối âm thanh của cuộc gọi

Theo dõi và quản lý các thiết bị đầu cuối âm thanh bằng cách sử dụng currentCallEndpoint, availableEndpointsisMuted Flow trong CallControlScope

fun observeAudioStateChanges(callControlScope: CallControlScope) {
    with(callControlScope) {
        launch { currentCallEndpoint.collect { /* Update UI */ } }
        launch { availableEndpoints.collect { /* Update UI */ } }
        launch { isMuted.collect { /* Handle mute state */ } }
    }
}

Thay đổi thiết bị âm thanh đang hoạt động bằng cách sử dụng biểu tượng requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

Hỗ trợ nền trước

Thư viện này sử dụng ConnectionService (Android 13 cấp độ API 33 trở xuống) hoặc foregroundtypes (Android 14 cấp độ API 34 trở lên) để hỗ trợ hoạt động ở nền trước.

Theo các yêu cầu về nền trước, ứng dụng phải đăng một thông báo để người dùng biết rằng ứng dụng đang chạy ở nền trước.

Để đảm bảo ứng dụng của bạn được ưu tiên thực thi trên nền trước, hãy tạo một thông báo sau khi bạn thêm lệnh gọi bằng nền tảng. Mức độ ưu tiên ở nền trước sẽ bị xoá khi ứng dụng của bạn kết thúc cuộc gọi hoặc thông báo của bạn không còn hợp lệ.

Tìm hiểu thêm về các dịch vụ trên nền trước.

Hỗ trợ từ xa cho Surface

Các thiết bị từ xa (đồng hồ thông minh, tai nghe Bluetooth, Android Auto) có thể quản lý cuộc gọi mà không cần tương tác trực tiếp với điện thoại. Ứng dụng của bạn phải triển khai các lambda gọi lại (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) được cung cấp cho CallsManager.addCall để xử lý các thao tác do những thiết bị này khởi tạo.

Khi một thao tác từ xa xảy ra, lambda tương ứng sẽ được gọi.

Hoàn tất thành công các tín hiệu lambda mà lệnh đã được xử lý. Nếu không tuân theo được lệnh, hàm lambda sẽ tạo ngoại lệ.

Việc triển khai đúng cách sẽ đảm bảo bạn có thể kiểm soát cuộc gọi một cách liền mạch trên nhiều thiết bị. Kiểm thử kỹ lưỡng với nhiều nền tảng từ xa.

Phần mở rộng về cuộc gọi

Ngoài việc quản lý trạng thái cuộc gọi và định tuyến âm thanh của các cuộc gọi, thư viện này còn hỗ trợ các tiện ích cuộc gọi. Đây là những tính năng không bắt buộc mà ứng dụng của bạn có thể triển khai để có trải nghiệm gọi phong phú hơn trên các nền tảng từ xa, chẳng hạn như Android Auto. Các tính năng này bao gồm phòng họp, chế độ im lặng cuộc gọi và các biểu tượng cuộc gọi khác. Khi ứng dụng của bạn triển khai một tiện ích, thông tin mà ứng dụng cung cấp sẽ được đồng bộ hoá với tất cả các thiết bị được kết nối cũng hỗ trợ việc hiển thị các tiện ích này trong giao diện người dùng. Điều này có nghĩa là người dùng cũng có thể tương tác với các tính năng này trên thiết bị từ xa.

Tạo cuộc gọi có phần mở rộng

Khi tạo một cuộc gọi, thay vì dùng CallManager#addCall để tạo cuộc gọi, bạn có thể dùng CallManager#addCallWithExtensions. Phương thức này cho phép ứng dụng truy cập vào một phạm vi khác có tên là ExtensionInitializationScope. Phạm vi này cho phép ứng dụng khởi tạo tập hợp các tiện ích không bắt buộc mà ứng dụng hỗ trợ. Ngoài ra, phạm vi này cung cấp thêm một phương thức là onCall, cung cấp một CallControlScope trở lại ứng dụng sau khi trao đổi và khởi chạy khả năng mở rộng hoàn tất.

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

Người tham gia cuộc gọi hỗ trợ

Nếu ứng dụng của bạn hỗ trợ người tham gia cuộc gọi cho các cuộc họp hoặc cuộc gọi nhóm, hãy dùng addParticipantExtension để khai báo việc hỗ trợ tiện ích này và dùng các API liên quan để cập nhật các nền tảng từ xa khi người tham gia thay đổi.

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

Ngoài việc thông báo cho các nền tảng từ xa về những người tham gia trong cuộc gọi, bạn cũng có thể cập nhật người tham gia đang hoạt động bằng cách sử dụng ParticipantExtension#updateActiveParticipant.

Ngoài ra, bạn cũng có thể thực hiện các thao tác không bắt buộc liên quan đến người tham gia cuộc gọi. Ứng dụng có thể sử dụng ParticipantExtension#addRaiseHandSupport để hỗ trợ khái niệm người tham gia giơ tay trong cuộc gọi và xem những người tham gia khác cũng giơ tay.

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

Tắt tiếng cuộc gọi hỗ trợ

Tính năng tắt chuông cuộc gọi cho phép người dùng yêu cầu ứng dụng tắt âm thanh cuộc gọi đi mà không cần tắt micrô của thiết bị theo cách thủ công. Tính năng này được quản lý theo từng cuộc gọi, vì vậy Jetpack sẽ xử lý sự phức tạp của việc quản lý trạng thái tắt tiếng toàn cầu của các cuộc gọi di động đang diễn ra trong khi cuộc gọi VOIP đang hoạt động. Điều này giúp giảm thiểu lỗi khi tắt tiếng âm thanh gửi đi trong các trường hợp có nhiều cuộc gọi, đồng thời cho phép các tính năng hữu ích như chỉ báo "bạn có đang nói không" khi người dùng đang nói mà không nhận ra rằng chế độ tắt tiếng cuộc gọi đang bật.

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

Biểu tượng cuộc gọi hỗ trợ

Biểu tượng cuộc gọi cho phép ứng dụng chỉ định một biểu tượng tuỳ chỉnh đại diện cho cuộc gọi sẽ xuất hiện trên các nền tảng từ xa trong cuộc gọi. Biểu tượng này cũng có thể được cập nhật trong suốt thời gian diễn ra cuộc gọi.

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

Tích hợp với nhật ký cuộc gọi của hệ thống

Core-Telecom cho phép ứng dụng của bạn tích hợp với nhật ký cuộc gọi của hệ thống. Tính năng này cho phép các cuộc gọi được thực hiện hoặc nhận bằng ứng dụng VoIP của bạn xuất hiện trong nhật ký cuộc gọi của trình quay số hệ thống, cho phép người dùng sử dụng trình quay số để bắt đầu cuộc gọi lại.

Bật tính năng tích hợp nhật ký cuộc gọi

Để bật tính năng này, ứng dụng của bạn phải đăng ký xử lý ý định TelecomManager.ACTION_CALL_BACK trong AndroidManifest.xml và cung cấp Activity để xử lý lệnh gọi lại. Ví dụ: giả sử VoipCallbackActivity là hoạt động xử lý lệnh gọi lại, thì tệp kê khai của bạn sẽ có dạng như sau:

Đăng ký ý định trong tệp kê khai

<activity
    android:name=".VoipCallbackActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.telecom.action.CALL_BACK" />
    </intent-filter>
</activity>

Xử lý ý định gọi lại

Khi người dùng bắt đầu một lệnh gọi lại từ trình quay số hệ thống, hoạt động đã đăng ký của bạn sẽ được khởi chạy bằng thao tác TelecomManager.ACTION_CALL_BACK. Ý định này chứa TelecomManager.EXTRA_UUID được cung cấp cho ứng dụng của bạn khi bạn thêm cuộc gọi ban đầu bằng CallsManager.

Phương thức CallControlScope getCallId sẽ trả về mã nhận dạng duy nhất này cho lệnh gọi.

Bạn nên lưu tất cả thông tin cần thiết để bắt đầu cuộc gọi do UUID xác định trong ứng dụng của mình (ví dụ: danh sách số điện thoại cho cuộc gọi nhóm) để có thể tạo lại và thực hiện lại cuộc gọi khi lệnh gọi lại được kích hoạt.

class VoipCallbackActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (intent?.action == TelecomManager.ACTION_CALL_BACK) {
            val uuidString = intent.getStringExtra(TelecomManager.EXTRA_UUID)
            if (uuidString != null) {
                val uuid = UUID.fromString(uuidString)
                
                // Retrieve your persisted call info using the UUID
                val callData = CallRepository.getCallByUuid(uuid)
                
                if (callData != null) {
                    // Place the call again using your app's logic
                    // ...
                }
            }
        }
        finish()
    }
}

Chọn không tích hợp nhật ký cuộc gọi

Theo mặc định, các cuộc gọi được ghi vào nhật ký cuộc gọi của hệ thống. Bạn có thể chọn không sử dụng tính năng này cho từng cuộc gọi bằng cách đặt tham số isLogged thành false khi tạo CallAttributesCompat:

val callAttributes = CallAttributesCompat(
    displayName = callerName,
    address = addressUri,
    direction = CallAttributesCompat.DIRECTION_OUTGOING,
    isLogExcluded = true // Opt-out of system call logging
)