Core-Telecom

Core-Telecom 라이브러리는 강력하고 일관된 API 세트를 제공하여 통화 애플리케이션을 Android 플랫폼과 통합하는 프로세스를 간소화합니다.

실제 구현을 살펴보고 싶다면 GitHub에서 샘플 애플리케이션을 확인하세요.

  • 경량 샘플 앱 - Core-Telecom API 사용을 보여주는 최소한의 예입니다. 기본 개념을 빠르게 이해하는 데 적합합니다.
  • 포괄적인 샘플 앱 (핵심 통신팀에서 개발): 고급 통신 기능과 권장사항을 보여주는 기능이 풍부한 애플리케이션입니다. 복잡한 통합 시나리오를 이해하는 데 유용한 리소스입니다.

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: 통화 주소 (예: 전화번호, 회의 링크)
  • 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
    )
}

통화 추가

CallAttributesCompat 및 콜백과 함께 callsManager.addCall를 사용하여 시스템에 새 통화를 추가하고 원격 화면 업데이트를 관리합니다. 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 내에서 DisconnectCause.REJECTED와 함께 disconnect()를 사용하여 전화를 거부합니다.

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 */ }
}

통화 연결 해제

DisconnectCause을 사용하여 disconnect()로 통화를 종료합니다.

disconnect(DisconnectCause(DisconnectCause.LOCAL))

통화 오디오 엔드포인트 관리

CallControlScope 내에서 currentCallEndpoint, availableEndpoints, isMuted 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 이상)를 사용합니다.

포그라운드 요구사항의 일환으로 애플리케이션이 포그라운드에서 실행되고 있음을 알리는 알림을 사용자에게 게시해야 합니다.

앱이 포그라운드 실행 우선순위를 갖도록 하려면 플랫폼과의 통화를 추가한 후 알림을 만드세요. 앱이 통화를 종료하거나 알림이 더 이상 유효하지 않으면 포그라운드 우선순위가 삭제됩니다.

포그라운드 서비스에 대해 자세히 알아보기

원격 Surface 지원

원격 기기 (스마트시계, 블루투스 헤드셋, Android Auto)는 직접 휴대전화 상호작용 없이 통화 관리가 가능합니다. 앱은 이러한 기기에서 시작된 작업을 처리하기 위해 CallsManager.addCall에 제공된 콜백 람다 (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive)를 구현해야 합니다.

원격 작업이 발생하면 해당 람다가 호출됩니다.

람다가 성공적으로 완료되면 명령어가 처리되었음을 나타냅니다. 명령어를 따를 수 없는 경우 람다에서 예외가 발생해야 합니다.

적절하게 구현하면 다양한 기기에서 원활하게 통화를 제어할 수 있습니다. 다양한 원격 표면으로 철저히 테스트합니다.

전화번호 광고 확장

이 라이브러리는 통화 상태와 통화의 오디오 라우팅을 관리할 뿐만 아니라 통화 확장 프로그램도 지원합니다. 통화 확장 프로그램은 Android Auto와 같은 원격 화면에서 더 풍부한 통화 환경을 위해 앱에서 구현할 수 있는 선택적 기능입니다. 이러한 기능에는 회의실, 통화 무음, 추가 통화 아이콘이 포함됩니다. 앱이 확장 프로그램을 구현하면 앱에서 제공하는 정보가 UI에서 이러한 확장 프로그램 표시를 지원하는 연결된 모든 기기와 동기화됩니다. 즉, 사용자가 상호작용할 수 있도록 원격 기기에서도 이러한 기능을 사용할 수 있습니다.

확장 프로그램으로 통화 만들기

통화를 만들 때 CallManager#addCall를 사용하여 통화를 만드는 대신 CallManager#addCallWithExtensions를 사용할 수 있습니다. 이렇게 하면 앱이 ExtensionInitializationScope라는 다른 범위에 액세스할 수 있습니다. 이 범위는 애플리케이션이 지원하는 선택적 확장 프로그램 집합을 초기화할 수 있도록 합니다. 또한 이 범위는 확장 프로그램 기능 교환 및 초기화가 완료된 후 CallControlScope을 앱에 제공하는 추가 메서드 onCall을 제공합니다.

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 애플리케이션을 사용하여 걸거나 받은 통화가 시스템 다이얼러 통화 기록에 표시되므로 사용자가 다이얼러를 사용하여 콜백을 시작할 수 있습니다.

통화 기록 통합 사용 설정

이 기능을 사용 설정하려면 애플리케이션이 AndroidManifest.xml에서 TelecomManager.ACTION_CALL_BACK 인텐트를 처리하도록 등록하고 콜백을 처리하는 Activity를 제공해야 합니다. 예를 들어 VoipCallbackActivity가 콜백을 처리하는 활동이라고 가정하면 매니페스트는 다음과 같이 표시됩니다.

매니페스트에 인텐트 등록

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

콜백 인텐트 처리

사용자가 시스템 다이얼러에서 콜백을 시작하면 등록된 활동이 TelecomManager.ACTION_CALL_BACK 작업으로 실행됩니다. 인텐트에는 CallsManager를 사용하여 통화를 처음 추가할 때 앱에 제공된 TelecomManager.EXTRA_UUID가 포함됩니다.

CallControlScope 메서드 getCallId는 호출의 이 고유 ID를 반환합니다.

콜백이 트리거될 때 통화를 다시 구성하고 걸 수 있도록 앱 내에서 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()
    }
}

통화 기록 통합 선택 해제

기본적으로 호출은 시스템 통화 로그에 기록됩니다. CallAttributesCompat을 만들 때 isLogged 매개변수를 false로 설정하여 호출별로 이 기능을 선택 해제할 수 있습니다.

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