Core-Telecom

Core-Telecom ไลบรารีช่วยให้กระบวนการผสานรวมแอปพลิเคชันการโทรกับแพลตฟอร์ม Android เป็นไปอย่างราบรื่นด้วยการจัดชุด API ที่มีประสิทธิภาพและสอดคล้องกัน

หากต้องการดูการใช้งานจริง คุณสามารถดูแอปพลิเคชันตัวอย่างได้ใน GitHub

  • แอปตัวอย่างแบบเบา - ตัวอย่างที่เล็กที่สุด ที่แสดงให้เห็นCore-Telecom การใช้งาน API เหมาะสำหรับการทำความเข้าใจ แนวคิดพื้นฐานอย่างรวดเร็ว
  • แอปตัวอย่างที่ครอบคลุม (พัฒนาโดยทีม Core-Telecom ) - แอปพลิเคชันที่มีฟีเจอร์มากมายซึ่งแสดงฟังก์ชันการทำงานขั้นสูงของ Telecom และแนวทางปฏิบัติแนะนำ ซึ่งเป็นแหล่งข้อมูลที่ยอดเยี่ยม ในการทำความเข้าใจสถานการณ์การผสานรวมที่ซับซ้อน

ตั้งค่า Core-Telecom

เพิ่มทรัพยากร Dependency androidx.core:core-telecom ลงในไฟล์ build.gradle ของแอป

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

ประกาศสิทธิ์ MANAGE_OWN_CALLS ใน AndroidManifest.xml ดังนี้

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

ลงทะเบียนแอปของคุณ

ลงทะเบียนแอปการโทรกับ Android โดยใช้ CallsManager เพื่อเริ่มเพิ่มการโทรลงในระบบ เมื่อลงทะเบียน ให้ระบุความสามารถของแอป (เช่น รองรับเสียง วิดีโอ) ดังนี้

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.addCall กับ CallAttributesCompat และ Callback เพื่อเพิ่มการโทรใหม่ลงในระบบและจัดการการอัปเดต Surface จากระยะไกล callControlScope ภายในบล็อก addCall จะช่วยให้แอปเปลี่ยนสถานะการโทร และรับข้อมูลอัปเดตเสียงได้เป็นหลัก ดังนี้

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.REJECTED ภายใน CallControlScope

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

จัดการอุปกรณ์ปลายทางเสียงในการโทร

สังเกตและจัดการอุปกรณ์ปลายทางเสียงโดยใช้ currentCallEndpoint, availableEndpoints และ isMuted 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 (ระดับ API 33 ของ Android 13 และต่ำกว่า) หรือ foregroundtypes (ระดับ API 34 ของ Android 14 ขึ้นไป) สำหรับการรองรับ เบื้องหน้า

แอปพลิเคชันต้องโพสต์การแจ้งเตือน เพื่อให้ผู้ใช้ทราบว่าแอปพลิเคชันกำลังทำงานอยู่เบื้องหน้า ซึ่งเป็นส่วนหนึ่งของข้อกำหนดเบื้องหน้า

หากต้องการให้แอปได้รับสิทธิ์ในการดำเนินการในเบื้องหน้าเป็นอันดับแรก ให้สร้างการแจ้งเตือนเมื่อเพิ่มการโทรด้วยแพลตฟอร์ม ระบบจะนำลำดับความสำคัญของเบื้องหน้าออกเมื่อแอปของคุณสิ้นสุดการโทรหรือการแจ้งเตือนไม่ถูกต้องอีกต่อไป

ดูข้อมูลเพิ่มเติมเกี่ยวกับบริการที่ทำงานอยู่เบื้องหน้า

การสนับสนุน Surface จากระยะไกล

อุปกรณ์ระยะไกล (สมาร์ทวอทช์ ชุดหูฟังบลูทูธ Android Auto) สามารถ จัดการการโทรได้โดยไม่ต้องโต้ตอบกับโทรศัพท์โดยตรง แอปของคุณต้องใช้ Lambda ของการเรียกกลับ (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) ที่ระบุไว้ใน CallsManager.addCall เพื่อจัดการการดำเนินการ ที่เริ่มต้นโดยอุปกรณ์เหล่านี้

เมื่อมีการดำเนินการจากระยะไกล ระบบจะเรียกใช้ 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)
        }
    }

ผสานรวมกับบันทึกการโทรของระบบ

Core-Telecom อนุญาตให้แอปพลิเคชันของคุณผสานรวมกับบันทึกการโทรของระบบ ฟีเจอร์นี้ช่วยให้การโทรที่โทรออกหรือรับสายโดยใช้แอปพลิเคชัน VoIP ปรากฏในประวัติการโทรของแป้นโทรศัพท์ของระบบ ซึ่งช่วยให้ผู้ใช้ใช้แป้นโทรศัพท์เพื่อเริ่มการโทรกลับได้

เปิดใช้การผสานรวมบันทึกการโทร

หากต้องการเปิดใช้ฟีเจอร์นี้ แอปพลิเคชันของคุณต้องลงทะเบียนเพื่อจัดการ Intent TelecomManager.ACTION_CALL_BACK ใน AndroidManifest.xml และระบุ Activity เพื่อประมวลผล Callback ตัวอย่างเช่น หาก VoipCallbackActivity เป็นกิจกรรมที่จัดการการเรียกกลับ ไฟล์ Manifest ควรมีลักษณะดังนี้

ลงทะเบียน Intent ในไฟล์ Manifest

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

จัดการ Intent ของการเรียกกลับ

เมื่อผู้ใช้เริ่มการโทรกลับจากแป้นหมุนหมายเลขของระบบ ระบบจะเปิดใช้กิจกรรมที่ลงทะเบียนไว้พร้อมกับการดำเนินการ TelecomManager.ACTION_CALL_BACK Intent มี TelecomManager.EXTRA_UUID ที่ให้ไว้กับแอปเมื่อ คุณเพิ่มการโทรโดยใช้ CallsManager ในตอนแรก

CallControlScope เมธอด getCallId จะแสดงรหัสที่ไม่ซ้ำกันนี้สำหรับการ เรียก

คุณควรบันทึกข้อมูลทั้งหมดที่จำเป็นต่อการเริ่มการโทรซึ่งระบุโดย UUID ภายในแอป (เช่น รายชื่อหมายเลขโทรศัพท์สำหรับการโทรแบบกลุ่ม) เพื่อให้ คุณสร้างใหม่และโทรอีกครั้งได้เมื่อมีการทริกเกอร์การโทรกลับ

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

เลือกไม่ใช้การผสานรวมบันทึกการโทร

โดยค่าเริ่มต้น ระบบจะบันทึกการโทรไปยังบันทึกการโทรของระบบ คุณเลือกไม่ใช้ฟีเจอร์นี้ได้ โดยตั้งค่าพารามิเตอร์ isLogged เป็น false เมื่อสร้าง CallAttributesCompat

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