Core-Telecom

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

실용적인 구현을 살펴보려면 GitHub에서 샘플 애플리케이션을 확인하세요.

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

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