텔레콤 프레임워크 개요

Android 텔레콤 프레임워크('텔레콤'이라고도 함)는 Android 지원 기기에서 음성 통화와 영상 통화를 관리합니다. 여기에는 전화 프레임워크를 사용하는 통화와 같은 SIM 기반 통화와 Core-Telecom Jetpack 라이브러리를 구현하는 VoIP 통화가 포함됩니다.

Telecom에서 관리하는 주요 구성요소는 ConnectionServiceInCallService입니다.

ConnectionService 구현은 PSTN과 같은 기술을 기반으로 다른 당사자에게 전화를 연결합니다. 휴대전화에서 가장 일반적인 ConnectionService 구현은 텔레포니 ConnectionService입니다. 이동통신사 통화를 연결합니다.

InCallService 구현은 텔레콤에서 관리하는 통화에 사용자 인터페이스를 제공하고 사용자가 이러한 통화를 제어하고 상호작용할 수 있도록 합니다. InCallService의 가장 일반적인 구현은 기기와 번들로 제공되는 전화 앱입니다.

통신사는 교환소 역할을 합니다. ConnectionService 구현에서 제공하는 호출을 InCallService 구현에서 제공하는 호출 사용자 인터페이스로 라우팅합니다.

다음과 같은 이유로 Telecom API를 구현할 수 있습니다.

대체 전화 앱 만들기

Android 기기에서 기본 전화 앱을 대체하려면 InCallService API를 구현합니다. 구현은 다음 요구사항을 충족해야 합니다.

  • 호출 기능이 없어야 하며 호출을 위한 사용자 인터페이스로만 구성되어야 합니다.
  • 텔레콤 프레임워크에서 인식하는 모든 호출을 처리해야 하며 호출의 성격을 가정해서는 안 됩니다. 예를 들어 통화가 SIM 기반 전화 통화라고 가정하거나 동영상 통화에 대한 전화 통신 제한 시행과 같이 특정 ConnectionService에 기반한 통화 제한을 구현해서는 안 됩니다.

자세한 내용은 InCallService를 참고하세요.

통화 솔루션 통합

통화 솔루션을 Android에 통합하려면 다음 옵션을 사용할 수 있습니다.

  • 자체 관리 Core-Telecom Jetpack 라이브러리 구현: 이 옵션은 기본 전화 앱 내에 통화를 표시하거나 사용자 인터페이스에 다른 통화를 표시하고 싶지 않은 독립형 통화 앱 개발자에게 적합합니다.

    Core-Telecom Jetpack 라이브러리와 통합하면 앱이 기기의 시스템 텔레포니 통화뿐만 아니라 Telecom과 통합된 다른 독립형 통화 앱과도 상호 운용할 수 있습니다. Core-Telecom 라이브러리는 오디오 라우팅 및 포커스도 관리합니다. 자세한 내용은 통화 앱 빌드를 참고하세요.

  • 관리형 ConnectionService API 구현: 이 옵션은 기존 기기 전화 애플리케이션을 사용하여 통화 사용자 인터페이스를 제공하는 통화 솔루션을 쉽게 개발할 수 있도록 지원합니다. SIP 통화 및 VoIP 통화 서비스의 서드 파티 구현이 여기에 해당합니다. 자세한 내용은 getDefaultDialerPackage()를 참조하세요.

    ConnectionService만으로는 통화를 연결하는 수단만 제공합니다. 연결된 사용자 인터페이스가 없습니다.

  • InCallService 및 ConnectionService API 모두 구현: 이 옵션은 자체 사용자 인터페이스를 갖춘 자체 ConnectionService 기반 통화 솔루션을 만들고 다른 모든 Android 통화도 동일한 사용자 인터페이스에 표시하려는 경우에 적합합니다. 이 접근 방식을 사용할 때 InCallService의 구현은 표시되는 호출의 소스에 관해 어떠한 가정도 해서는 안 됩니다. 또한 기본 전화 앱이 맞춤 InCallService로 설정되어 있지 않아도 ConnectionService 구현이 계속 작동해야 합니다.

통화 선택

Android 10 (API 수준 29) 이상을 실행하는 기기에서는 앱이 사용자의 주소록에 없는 번호의 전화를 잠재적 스팸 전화로 식별할 수 있습니다. 사용자는 스팸 전화를 자동으로 거부하도록 선택할 수 있습니다. 사용자가 통화를 놓쳤을 때 더 투명하게 정보를 제공하기 위해 이러한 차단된 전화에 관한 정보는 통화 기록에 기록됩니다. Android 10 API를 사용하면 통화 선택 및 발신번호 표시 기능을 제공하기 위해 사용자에게 READ_CALL_LOG 권한을 얻을 필요가 없습니다.

CallScreeningService 구현을 사용하여 통화를 선별합니다. 번호가 사용자의 연락처 목록에 없는 경우 새로 수신 또는 발신되는 모든 전화에 대해 onScreenCall() 함수를 호출합니다. Call.Details 객체에서 호출에 관한 정보를 확인할 수 있습니다. 특히 getCallerNumberVerificationStatus() 함수에는 다른 번호에 관한 네트워크 제공업체의 정보가 포함됩니다. 인증 상태가 실패하면 잘못된 번호에서 걸려온 전화이거나 스팸 전화일 가능성이 높습니다.

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

자바

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

onScreenCall() 함수를 respondToCall()를 호출하도록 설정하여 시스템에 새 호출에 응답하는 방법을 알려줍니다. 이 함수는 시스템에 통화를 차단하도록 지시하거나, 사용자가 거부한 것처럼 거부하거나, 음소거하도록 지시하는 데 사용할 수 있는 CallResponse 매개변수를 사용합니다. 시스템에 이 통화를 기기의 통화 기록에 추가하는 것을 건너뛰도록 지시할 수도 있습니다.

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

자바

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

시스템에서 CallScreeningService 구현을 올바르게 트리거할 수 있도록 매니페스트 파일에 적절한 인텐트 필터와 권한으로 CallScreeningService 구현을 등록해야 합니다.

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

전화 리디렉션

Android 10 이상을 실행하는 기기는 Android 9 이하를 실행하는 기기와 다르게 호출 인텐트를 관리합니다. Android 10 이상에서는 ACTION_NEW_OUTGOING_CALL 브로드캐스트가 지원 중단되고 CallRedirectionService API로 대체되었습니다. CallRedirectionService는 Android 플랫폼에서 실행하는 발신 전화를 수정하는 데 사용할 수 있는 인터페이스를 제공합니다. 예를 들어 서드 파티 앱이 통화를 취소하고 VoIP를 통해 통화 경로를 변경할 수 있습니다.

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

자바

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

시스템에서 이 서비스를 올바르게 시작할 수 있도록 매니페스트에 이 서비스를 등록해야 합니다.

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

리디렉션 서비스를 사용하려면 앱에서 RoleManager에서 통화 리디렉션 역할을 요청해야 합니다. 그러면 사용자에게 앱에서 통화 리디렉션을 처리하도록 허용할지 묻는 메시지가 표시됩니다. 앱에 이 역할이 부여되지 않으면 리디렉션 서비스가 사용되지 않습니다.

필요한 경우 요청할 수 있도록 사용자가 앱을 실행할 때 앱에 이 역할이 있는지 확인해야 합니다. RoleManager에서 만든 인텐트를 실행하므로 onActivityResult() 함수를 재정의하여 사용자 선택을 처리해야 합니다.

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}