기본 전화 애플리케이션 빌드

기본 전화 애플리케이션을 사용하면 Android 텔레콤 프레임워크가 역할 관리자 및 통화 중 서비스를 사용하여 애플리케이션에 통화 상태를 알리고 Android 기기의 기본 전화 앱을 대체할 수 있는 항목을 생성하고 InCallService API를 구현할 수 있습니다. 구현은 다음 요구사항을 충족해야 합니다.

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

통화 앱을 통해 사용자는 기기에서 음성 통화 또는 영상 통화를 받거나 걸 수 있습니다. 다음 스크린샷과 같이 통화 앱은 통화 시 기본 전화 앱 인터페이스를 사용하는 대신 자체 사용자 인터페이스를 사용합니다.

통화 앱의 예
자체 사용자 인터페이스를 사용하는 통화 앱의 예

Android 프레임워크에는 android.telecom 패키지가 포함되어 있으며 이 패키지에는 텔레콤 프레임워크에 따라 통화 앱을 빌드하는 데 도움이 되는 클래스가 포함되어 있습니다. 텔레콤 프레임워크에 따라 앱을 빌드하면 다음과 같은 이점이 있습니다.

  • 앱이 기기의 네이티브 텔레콤 하위 시스템과 올바르게 상호 운용됩니다.
  • 앱이 프레임워크를 준수하는 다른 통화 앱과도 올바르게 상호 운용됩니다.
  • 프레임워크를 통해 앱이 음성 및 영상 라우팅을 관리할 수 있습니다.
  • 프레임워크를 통해 앱이 통화에 포커스가 있는지 여부를 확인할 수 있습니다.

매니페스트 선언 및 권한

앱 매니페스트에서 다음 예와 같이 앱이 MANAGE_OWN_CALLS 권한을 사용한다고 선언합니다.

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

앱 권한 선언에 관한 자세한 내용은 권한을 참조하세요.

앱에서 ConnectionService 클래스를 구현하는 클래스를 지정하는 서비스를 선언해야 합니다. 텔레콤 하위 시스템에서는 서비스가 시스템에 결합할 수 있도록 BIND_TELECOM_CONNECTION_SERVICE 권한을 선언해야 합니다. 다음 예는 앱 매니페스트에서 서비스를 선언하는 방법을 보여줍니다.

<service android:name="com.example.MyConnectionService"
    android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.ConnectionService" />
    </intent-filter>
</service>

서비스를 비롯한 앱 구성요소 선언에 관한 자세한 내용은 앱 구성요소를 참조하세요.

연결 서비스 구현

통화 앱은 텔레콤 하위 시스템이 결합할 수 있는 ConnectionService 클래스의 구현을 제공해야 합니다. ConnectionService 구현은 다음 메서드를 재정의해야 합니다.

onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)

텔레콤 하위 시스템은 새 발신 전화를 걸기 위해 placeCall(Uri, Bundle)을 호출하는 앱에 응답하여 이 메서드를 호출합니다. 앱은 새 발신 전화를 나타내는 Connection 클래스 구현의 새 인스턴스를 반환합니다. 자세한 내용은 연결 구현을 참조하세요. 다음 작업을 실행하여 발신 연결을 더욱 상세하게 맞춤설정할 수 있습니다.

onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

앱에서 placeCall(Uri, Bundle) 메서드를 호출했는데 발신 전화를 걸 수 없을 때 텔레콤 하위 시스템은 이 메서드를 호출합니다. 이 상황에 대응하여 앱은 발신 전화를 걸 수 없다는 것을 사용자에게 알려야 합니다(예: 알림 상자 또는 토스트 메시지 사용). 진행 중인 긴급 전화가 있거나 다른 앱에서 먼저 진행 중인 통화를 보류할 수 없다면 앱에서 전화를 걸지 못할 수 있습니다.

onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)

앱에서 addNewIncomingCall(PhoneAccountHandle, Bundle) 메서드를 호출하여 앱의 새로운 수신 전화를 시스템에 알릴 때 텔레콤 하위 시스템은 이 메서드를 호출합니다. 앱은 새 수신 전화를 나타내는 Connection 구현의 새 인스턴스를 반환합니다. 자세한 내용은 연결 구현을 참조하세요. 다음 작업을 실행하여 수신 연결을 더욱 상세하게 맞춤설정할 수 있습니다.

onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

앱에서 addNewIncomingCall(PhoneAccountHandle, Bundle) 메서드를 호출하여 새로운 수신 전화를 텔레콤에 알렸으나 수신 전화가 허용되지 않을 때 텔레콤 하위 시스템이 이 메서드를 호출합니다. 자세한 내용은 통화 제약을 참조하세요. 앱에서 자동으로 수신 전화를 거부하고 부재중 전화를 사용자에게 알리는 알림을 게시할 수도 있습니다.

연결 구현

앱에서 Connection의 서브클래스를 생성하여 앱의 통화를 나타내야 합니다. 구현 시 다음 메서드를 재정의해야 합니다.

onShowIncomingCallUi()

새 수신 전화가 추가되어 앱에 수신 전화 UI를 표시해야 할 때 텔레콤 하위 시스템은 이 메서드를 호출합니다.

onCallAudioStateChanged(CallAudioState)

텔레콤 하위 시스템은 이 메서드를 호출하여 현재 오디오 경로 또는 모드가 변경되었다는 것을 앱에 알립니다. 앱에서 setAudioRoute(int) 메서드를 사용하여 오디오 모드를 변경하는 것에 대응하여 이 메서드가 호출됩니다. 또한 시스템이 오디오 경로를 변경하면(예: 블루투스 헤드셋 연결이 끊겼을 때) 이 메서드가 호출될 수도 있습니다.

onHold()

텔레콤 하위 시스템은 통화를 대기 상태로 전환하려고 할 때 이 메서드를 호출합니다. 이 요청에 대응하여 앱은 통화를 대기 상태로 전환한 후 setOnHold() 메서드를 호출하여 통화가 대기 중이라는 것을 시스템에 알려야 합니다. 통화를 표시하는 Android Auto와 같은 통화 중 서비스에서 통화를 대기 상태로 전환하려는 사용자 요청을 전달하려고 할 때 텔레콤 하위 시스템은 이 메서드를 호출할 수 있습니다. 또한 사용자가 다른 앱에서 통화를 활성화할 때에도 텔레콤 하위 시스템은 이 메서드를 호출합니다. 통화 중 서비스에 관한 자세한 내용은 InCallService를 참조하세요.

onUnhold()

텔레콤 하위 시스템은 대기 상태의 통화를 재개하려고 할 때 이 메서드를 호출합니다. 앱에서 통화를 재개하면 setActive() 메서드를 호출하여 통화가 더 이상 대기 상태가 아니라는 것을 시스템에 알려야 합니다. 통화를 표시하는 Android Auto와 같은 통화 중 서비스에서 통화 재개 요청을 전달하려고 할 때 텔레콤 하위 시스템은 이 메서드를 호출할 수 있습니다. 통화 중 서비스에 관한 자세한 내용은 InCallService를 참조하세요.

onAnswer()

텔레콤 하위 시스템은 이 메서드를 호출하여 수신 전화에 응답해야 한다는 것을 앱에 알립니다. 앱에서 통화에 응답하면 setActive() 메서드를 호출하여 통화에 응답했다는 것을 시스템에 알려야 합니다. 앱에서 새 수신 전화를 추가했는데 다른 앱에서 이미 진행 중인 통화를 보류할 수 없을 때 텔레콤 하위 시스템은 이 메서드를 호출할 수 있습니다. 이럴 때 텔레콤 하위 시스템은 앱을 대신하여 수신 전화 UI를 표시합니다. 프레임워크는 전화에 응답할 영상 상태를 지정할 수 있도록 지원하는 오버로드된 메서드를 제공합니다. 자세한 내용은 onAnswer(int)를 참고하세요.

onReject()

텔레콤 하위 시스템은 수신 전화를 거부하려고 할 때 이 메서드를 호출합니다. 앱에서 통화를 거부하면 setDisconnected(DisconnectCause)를 호출하고 REJECTED를 매개변수로 지정해야 합니다. 그런 다음 앱에서 destroy() 메서드를 호출하여 통화를 처리했다는 것을 시스템에 알려야 합니다. 사용자가 앱에서 수신 전화를 거부했을 때 텔레콤 하위 시스템은 이 메서드를 호출합니다.

onDisconnect()

텔레콤 하위 시스템은 통화 연결을 끊으려고 할 때 이 메서드를 호출합니다. 호출이 종료되면 앱에서 setDisconnected(DisconnectCause) 메서드를 호출하고 LOCAL를 매개변수로 지정하여 사용자 요청으로 인해 통화 연결이 끊어졌음을 나타내야 합니다. 그런 다음 앱에서 destroy() 메서드를 호출하여 통화를 처리했다는 것을 텔레콤 하위 시스템에 알려야 합니다. 사용자가 Android Auto와 같은 다른 통화 중 서비스를 통해 통화 연결을 끊었을 때 시스템이 이 메서드를 호출할 수 있습니다. 또한 사용자가 긴급 전화를 걸려고 하는 상황과 같이 새 전화를 걸기 위해 기존 통화 연결을 끊어야 하는 때에도 시스템은 이 메서드를 호출합니다. 통화 중 서비스에 관한 자세한 내용은 InCallService를 참조하세요.

일반적인 통화 처리 시나리오

호출 흐름의 ConnectionService API를 사용하면 android.telecom 패키지의 다른 클래스와 상호작용하게 됩니다. 다음 섹션에서는 일반적인 통화 시나리오 및 앱에서 API를 사용하여 통화를 처리하는 방법을 설명합니다.

수신 전화 받기

수신 전화를 처리하는 흐름은 다른 앱에 통화가 있는지 여부에 따라 변경됩니다. 흐름의 차이가 생기는 이유는 기기의 모든 통화 앱에 안정적인 환경을 보장하기 위해 다른 앱에 진행 중인 통화가 있을 때 텔레콤 프레임워크가 일부 제약을 설정해야 하기 때문입니다. 자세한 내용은 통화 제약을 참조하세요.

다른 앱에 진행 중인 통화가 없을 때

다른 앱에 진행 중인 통화가 없을 때 수신 전화를 받으려면 다음 단계를 따르세요.

  1. 앱에서 일반적인 메커니즘을 사용하여 새로운 수신 전화를 받습니다.
  2. addNewIncomingCall(PhoneAccountHandle, Bundle) 메서드를 사용하여 새 수신 전화에 관해 텔레콤 하위 시스템에 알립니다.
  3. 텔레콤 하위 시스템은 앱의 ConnectionService 구현에 결합하고 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 메서드를 사용하여 새 수신 전화를 나타내는 Connection 클래스의 새 인스턴스를 요청합니다.
  4. 텔레콤 하위 시스템은 onShowIncomingCallUi() 메서드를 사용하여 수신 전화 사용자 인터페이스를 표시해야 한다는 것을 앱에 알립니다.
  5. 앱은 관련 전체 화면 인텐트가 있는 알림을 사용하여 수신 전화 UI를 표시합니다. 자세한 내용은 onShowIncomingCallUi()를 참조하세요.
  6. 사용자가 수신 전화를 수락하면 setActive() 메서드를 호출하거나 사용자가 수신 전화를 거부하면 REJECTED를 매개변수로 지정하여 setDisconnected(DisconnectCause)를 호출한 후 destroy() 메서드를 호출합니다.

다른 앱에서 진행 중인 통화를 보류할 수 없을 때

다른 앱에서 진행 중인 통화를 보류할 수 없을 때 수신 전화를 받으려면 다음 단계를 따르세요.

  1. 앱에서 일반적인 메커니즘을 사용하여 새로운 수신 전화를 받습니다.
  2. addNewIncomingCall(PhoneAccountHandle, Bundle) 메서드를 사용하여 새 수신 전화에 관해 텔레콤 하위 시스템에 알립니다.
  3. 텔레콤 하위 시스템은 앱의 ConnectionService 구현에 결합하고 onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 메서드를 사용하여 새 수신 전화를 나타내는 Connection 객체의 새 인스턴스를 요청합니다.
  4. 텔레콤 하위 시스템은 수신 전화를 위해 수신 전화 UI를 표시합니다.
  5. 사용자가 통화를 수락하면 텔레콤 하위 시스템은 onAnswer() 메서드를 호출합니다. setActive() 메서드를 호출하여 이제 통화가 연결되었음을 텔레콤 하위 시스템에 알려야 합니다.
  6. 사용자가 통화를 거부하면 텔레콤 하위 시스템은 onReject() 메서드를 호출합니다. REJECTED를 매개변수로 지정하여 setDisconnected(DisconnectCause) 메서드를 호출한 후 destroy() 메서드를 호출해야 합니다.

발신 전화 걸기

발신 전화를 걸기 위한 흐름에는 텔레콤 프레임워크에 의해 부과된 제약으로 인해 전화를 걸지 못할 가능성을 처리하는 것이 포함됩니다. 자세한 내용은 통화 제약을 참조하세요.

발신 전화를 걸려면 다음 단계를 따르세요.

  1. 사용자가 앱 내에서 발신 전화를 시작합니다.
  2. placeCall(Uri, Bundle) 메서드를 사용하여 새 발신 전화에 관해 텔레콤 하위 시스템에 알립니다. 메서드 매개변수와 관련하여 다음을 고려합니다.
    • Uri 매개변수는 전화를 걸고 있는 대상 주소를 나타냅니다. 일반 전화번호에는 tel: URI 스키마를 사용합니다.
    • Bundle 매개변수를 사용하면 EXTRA_PHONE_ACCOUNT_HANDLE 엑스트라에 앱의 PhoneAccountHandle 객체를 추가하여 통화 앱에 관한 정보를 제공할 수 있습니다. 앱은 모든 발신 전화에 PhoneAccountHandle 객체를 제공해야 합니다.
    • 또한 Bundle 매개변수를 사용하면 EXTRA_START_CALL_WITH_VIDEO_STATE 엑스트라에 STATE_BIDIRECTIONAL 값을 지정하여 발신 전화에 영상을 포함할지 여부도 지정할 수 있습니다. 기본적으로 텔레콤 하위 시스템은 영상 통화를 스피커폰으로 라우팅합니다.
  3. 텔레콤 하위 시스템은 앱의 ConnectionService 구현에 결합합니다.
  4. 앱에서 발신 전화를 걸 수 없다면 텔레콤 하위 시스템은 onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) 메서드를 호출하여 현재는 전화를 걸 수 없다는 것을 앱에 알립니다. 앱은 전화를 걸 수 없다는 것을 사용자에게 알려야 합니다.
  5. 앱에서 발신 전화를 걸 수 있다면 텔레콤 하위 시스템은 onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) 메서드를 호출합니다. 앱은 새 발신 전화를 나타내는 Connection 클래스의 인스턴스를 반환해야 합니다. 연결 시 설정해야 하는 속성에 관한 자세한 내용은 연결 서비스 구현을 참조하세요.
  6. 발신 전화가 연결되면 setActive() 메서드를 호출하여 통화가 활성화되었다는 것을 텔레콤 하위 시스템에 알립니다.

통화 종료

통화를 종료하려면 다음 단계를 따르세요.

  1. 사용자가 통화를 종료하면 LOCAL을 매개변수로 전달하여 setDisconnected(DisconnectCause)를 호출하고 상대방이 통화를 종료하면 REMOTE를 매개변수로 전달합니다.
  2. destroy() 메서드를 호출합니다.

통화 제약

사용자에게 일관되고 간편한 통화 환경을 보장하기 위해 텔레콤 프레임워크는 기기에서 통화를 관리하기 위한 몇 가지 제약을 적용합니다. 예를 들어 사용자가 자체 관리 ConnectionService API를 구현하는 두 가지 통화 앱인 FooTalk 및 BarTalk을 설치했다고 생각해 보세요. 이 경우 다음과 같은 제약이 적용됩니다.

  • API 레벨 27 이하에서 실행되는 기기에서는 한번에 하나의 앱만 진행 중인 통화를 유지할 수 있습니다. 이 제약은 사용자가 FooTalk 앱을 사용하여 통화를 진행하는 동안 BarTalk 앱은 새로운 통화를 시작하거나 전화를 받을 수 없다는 것을 의미합니다.

    API 레벨 28 이상에서 실행되는 기기에서는 FooTalk 및 BarTalk 앱 모두에서 CAPABILITY_SUPPORT_HOLDCAPABILITY_HOLD 권한을 선언하면 사용자가 앱 간을 전환하며 또 다른 통화를 시작하거나 전화를 받아 진행 중인 통화를 둘 이상 유지할 수 있습니다.

  • 사용자가 일반 관리 통화(예: 기본 제공 전화 또는 다이얼러 앱 사용)를 사용 중이라면 통화 앱에서 시작된 통화를 사용할 수 없습니다. 즉, 사용자가 이동통신사를 통해 일반 통화를 하는 중에 FooTalk 또는 BarTalk 통화를 동시에 할 수 없습니다.

  • 사용자가 긴급 전화를 걸면 텔레콤 하위 시스템은 앱의 통화 연결을 끊습니다.

  • 사용자가 긴급 전화를 하는 동안에는 앱에서 전화를 받거나 걸 수 없습니다.

  • 앱에 수신 전화가 걸려올 때 다른 통화 앱에서 진행 중인 통화가 있는데 수신 전화를 받으면 다른 앱에서 진행 중인 통화는 종료됩니다. 앱에 일반적인 수신 전화 사용자 인터페이스가 표시되어서는 안 됩니다. 텔레콤 프레임워크는 수신 전화 사용자 인터페이스를 표시하여 새로운 전화를 받으면 진행 중인 통화가 종료됨을 사용자에게 알립니다. 즉, 사용자가 FooTalk 통화 중인데 BarTalk 앱에 수신 전화가 걸려올 경우 텔레콤 프레임워크는 사용자에게 새로운 BarTalk 수신 전화가 왔으며 이를 받으면 기존 FooTalk 통화가 종료된다고 알려줍니다.

기본 전화 앱이 되기

기본 다이얼러/전화 앱은 기기가 통화 중일 때 통화 중 사용자 인터페이스를 제공합니다. 또한 사용자가 통화를 시작하고 기기에서 통화 기록을 확인할 수 있는 수단을 제공합니다. 기기는 시스템에서 제공하는 기본 다이얼러/전화 앱과 함께 번들로 제공됩니다. 사용자는 시스템 앱에서 이 역할을 인계받을 단일 앱을 선택할 수 있습니다. 이 역할을 수행하려는 앱은 RoleManager를 사용하여 RoleManager.ROLE_DIALER 역할을 채우도록 요청합니다.

기본 전화 앱은 기기가 통화 중일 때 사용자 인터페이스를 제공하고 기기가 자동차 모드에 있지 않습니다 (즉, UiModeManager#getCurrentModeType()Configuration.UI_MODE_TYPE_CAR가 아님).

RoleManager.ROLE_DIALER 역할을 충족하려면 앱이 여러 요구사항을 충족해야 합니다.

  • Intent#ACTION_DIAL 인텐트를 처리해야 합니다. 즉, 사용자가 발신 전화를 시작할 수 있도록 앱은 다이얼패드 UI를 제공해야 합니다.
  • InCallService API를 완전히 구현해야 하며 수신 전화 UI와 진행 중인 호출 UI를 모두 제공해야 합니다.

참고: RoleManager.ROLE_DIALER를 채우는 앱이 바인딩 중에 null InCallService를 반환하면 텔레콤 프레임워크는 자동으로 기기에 미리 로드된 다이얼러 앱을 사용하도록 돌아갑니다. 시스템에서 사용자에게 알림이 표시되어 미리 로드된 다이얼러 앱을 사용하여 통화가 계속 진행되었음을 알려줍니다. 앱은 null 바인딩을 반환해서는 안 됩니다. 반환하면 RoleManager.ROLE_DIALER의 요구사항을 충족하지 못합니다.

참고: 앱이 RoleManager.ROLE_DIALER를 채우고 런타임에 변경하여 더 이상 이 역할의 요구사항을 충족하지 못하는 경우 RoleManager가 자동으로 역할에서 앱을 삭제하고 앱을 닫습니다. 예를 들어 PackageManager.setComponentEnabledSetting(ComponentName, int, int)를 사용하여 앱이 매니페스트에서 선언하는 InCallService를 프로그래매틱 방식으로 사용 중지하면 앱은 RoleManager.ROLE_DIALER에 필요한 요구사항을 더 이상 충족하지 못합니다.

미리 로드된 다이얼러는 앱이 RoleManager.ROLE_DIALER 역할을 채우더라도 사용자가 긴급 전화를 걸 때 항상 사용됩니다. 긴급 전화를 걸 때 최적의 환경을 보장하려면 기본 다이얼러에서는 긴급 전화를 비롯한 전화를 걸 때 항상 TelecomManager.placeCall(Uri, Bundle)를 사용해야 합니다. 이렇게 하면 플랫폼에서 요청이 기본 다이얼러에서 왔는지 확인할 수 있습니다. 미리 로드되지 않은 다이얼러 앱이 Intent#ACTION_CALL를 사용하여 긴급 전화를 걸면 확인을 위해 Intent#ACTION_DIAL를 사용하여 미리 로드된 다이얼러 앱으로 올라갑니다. 이는 사용자 환경이 최적화되지 않습니다.

다음은 InCallService의 매니페스트 등록 예입니다. 메타데이터 TelecomManager#METADATA_IN_CALL_SERVICE_UI는 이 특정 InCallService 구현이 내장된 호출 중 UI를 대체하려고 함을 나타냅니다. 메타데이터 TelecomManager#METADATA_IN_CALL_SERVICE_RINGING는 이 InCallService가 수신 전화의 벨소리를 재생한다는 것을 나타냅니다. 수신 전화 UI 표시 및 앱에서 벨소리 재생에 관한 자세한 내용은 아래를 참고하세요.

 <service android:name="your.package.YourInCallServiceImplementation"
          android:permission="android.permission.BIND_INCALL_SERVICE"
          android:exported="true">
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
      <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
          android:value="true" />
      <intent-filter>
          <action android:name="android.telecom.InCallService"/>
      </intent-filter>
 </service>

참고: InCallServiceandroid:exported="false" 속성으로 표시하면 안 됩니다. 그렇게 하면 호출 중에 구현에 바인딩되지 않을 수 있습니다.

InCallService API를 구현하는 것 외에도 매니페스트에서 Intent#ACTION_DIAL 인텐트를 처리하는 활동을 선언해야 합니다. 아래 예에서는 그 방법을 보여줍니다.

 <activity android:name="your.package.YourDialerActivity"
           android:label="@string/yourDialerActivityLabel">
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <intent-filter>
           <action android:name="android.intent.action.DIAL" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:scheme="tel" />
      </intent-filter>
 </activity>

사용자가 애플리케이션을 설치하고 처음 실행하는 경우 RoleManager를 사용하여 앱을 새로운 기본 전화 앱으로 설정할지 묻는 메시지를 사용자에게 표시해야 합니다.

아래 코드는 앱에서 기본 전화/다이얼러 앱이 되도록 요청하는 방법을 보여줍니다.

 private static final int REQUEST_ID = 1;

 public void requestRole() {
     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
     startActivityForResult(intent, REQUEST_ID);
 }

 public void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == REQUEST_ID) {
         if (resultCode == android.app.Activity.RESULT_OK) {
             // Your app is now the default dialer app
         } else {
             // Your app is not the default dialer app
         }
     }
 }

웨어러블 기기용 InCallService 액세스

    앱이 서드 파티 호환 앱이며 InCallService API에 액세스하려는 경우 앱에서 할 수 있는 작업은 다음과 같습니다.

    1. 매니페스트에서 MANAGE_ONGOING_CALLS 권한 선언
    2. CompanionDeviceManager API를 호환 앱으로 통해 실제 웨어러블 기기와 연결합니다. https://developer.android.com/guide/topics/connectivity/companion-device-pairing을 참조하세요.
    3. BIND_INCALL_SERVICE 권한으로 이 InCallService 구현

수신 전화 알림 표시

앱이 InCallService#onCallAdded(Call)를 통해 새 수신 전화를 받으면 수신 전화의 수신 전화 UI를 표시합니다. 이때 NotificationManager API를 사용하여 새 수신 전화 알림을 게시해야 합니다.

앱이 메타데이터 TelecomManager#METADATA_IN_CALL_SERVICE_RINGING를 선언하는 경우 수신 전화의 벨소리 재생을 담당합니다. 앱에서는 원하는 벨소리를 지정하는 NotificationChannel를 만들어야 합니다. 예:

 NotificationChannel channel = new NotificationChannel(YOUR_CHANNEL_ID, "Incoming Calls",
          NotificationManager.IMPORTANCE_MAX);
 // other channel setup stuff goes here.

 // We'll use the default system ringtone for our incoming call notification channel.  You can
 // use your own audio resource here.
 Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
 channel.setSound(ringtoneUri, new AudioAttributes.Builder()
          // Setting the AudioAttributes is important as it identifies the purpose of your
          // notification sound.
          .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
          .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
      .build());

 NotificationManager mgr = getSystemService(NotificationManager.class);
 mgr.createNotificationChannel(channel);

앱이 새 수신 전화를 받으면 수신 전화의 Notification를 만들어 수신 전화 알림 채널과 연결합니다. 알림에 PendingIntent를 지정하여 전체 화면 수신 전화 UI를 실행할 수 있습니다. 알림 관리자 프레임워크는 사용자가 휴대전화를 사용 중일 때 알림을 헤드업 알림으로 표시합니다. 사용자가 휴대전화를 사용하지 않는 경우 전체 화면 수신 전화 UI가 대신 사용됩니다. 예:

 // Create an intent which triggers your fullscreen incoming call user interface.
 Intent intent = new Intent(Intent.ACTION_MAIN, null);
 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
 intent.setClass(context, YourIncomingCallActivity.class);
 PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 // Build the notification as an ongoing high priority item; this ensures it will show as
 // a heads up notification which slides down over top of the current content.
 final Notification.Builder builder = new Notification.Builder(context);
 builder.setOngoing(true);
 builder.setPriority(Notification.PRIORITY_HIGH);
 // Set notification content intent to take user to the fullscreen UI if user taps on the
 // notification body.
 builder.setContentIntent(pendingIntent);
 // Set full screen intent to trigger display of the fullscreen UI when the notification
 // manager deems it appropriate.
 builder.setFullScreenIntent(pendingIntent, true);
 // Setup notification content.
 builder.setSmallIcon( yourIconResourceId );
 builder.setContentTitle("Your notification title");
 builder.setContentText("Your notification content.");
 // Use builder.addAction(..) to add buttons to answer or reject the call.
 NotificationManager notificationManager = mContext.getSystemService(
     NotificationManager.class);
 notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
```