본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] ConnectionService 에 대해 알아보자

by 돼지왕 왕돼지 2023. 8. 17.
반응형


https://developer.android.com/reference/android/telecom/ConnectionService

#
앱이 다음 조건을 충족할 때 구현해야 하는 Service 임

  1. VoIP 이나 다른 전화를 발신하면서 system 이 관리하는 ConnectionService 에 통합되길 원하는 경우
  2. 독립적인 전화앱으로 system 이 관리하는 ConnectionService 에 통합되지 않길 원하는 경우 (self managed)

 

#
ConnectionService 를 구현하면, 다음 과정을 통해 Telecom 이 bind 하도록 등록 할 수 있다.

1. Manifest 에 정의

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

 

2. TelecomManager 에 PhoneAccount 등록
System 이 관리하는 ConnectionService 는 Telecom 이 bind 하기 전에 user 에 의해 phone app setting 을 통해 enable 되어야만 한다.
Self-managed ConnectionService 는 Manifest.permission.MANAGE_OWN_CALLS 를 manifest 에 설정해야만 Telecom 이 bind 할 수 있다.

PhoneAccount 등록 후, user 에 의해 phone app setting 에서 enable 되거나 권한을 획득하면, telecom 은 ConnectionService 에 bind 되어 TelecomManager.placeCall(Uri, Bundle) 또는 TelecomManager.addNewIncomingCall(PhoneAccountHandle, Bundle) 을 통해 새로운 전화가 왔음을 감지할 수 있다. (돼왕 : placeCall 은 outgoing, addNewIncomingCall 은 incoming)

ConnectionService 는 call 에 대해 onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest), onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest) 가 호출되는 것도 기대할 수 있으며, 이 때 새로운 Connection 객체를 제공해야만 한다.
이 Connection 객체를 통해 telecom 은 state update 를 받을 수 있고, ConnectionService 는 answer, reject, hold, disconnect 등의 call command 도 받을 수 있다.

 

 

Self-Managed Connection Services

#
VoIP 앱은 ConnectionService 를 구현하여 Call 이 Android platform 과 통합되도록 할 수 있다.
이를 통한 장점은 아래와 같다.

  • 병렬 Call 다루기 - user 는 다른 앱에서 call 간 swap 을 할 수 있다.
  • 간단해진 audio routing - platform 은 통합된 available 한 audio route 를 제공하고, (Connection.onAvailableCallEndpointsChanged(List)), 표준화된 audio route switch 방법을 제공한다. (Connection.requestCallEndpointChange(CallEndPoint, Executor, OutcomeReceiver)).
  • Bluetooth 통합 - 블루투스 단말에서 call 이 보여질 수 있다. (car head, headset 등)
  • 주변 기기 통합 - InCallService 를 구현한 wearable 단말들이 optional 하게 self-managed call 을 구독해볼 수 있다. wearable 도 일반적인 call UX 를 사용해서 hangup, answer, reject 등을 할 수 있다.
  • 자동차의 통화 경험 - call 이 차량에서도 보여지고 컨트롤 될 수 있다. 이는 call metadata 에 의해 좌우된다.

 

 

Registring a Phone Account

#
수발신 Telecom call 을 다루기 전에 Telecom 에 앱이 call 을 다룰 능력이 있다는 것을 PhoneAccount 등록을 통해 알려야 한다.
앱은 다음 요구사항을 만족하는 PhoneAccount 객체를 생성해야 한다.

  • PhoneAccount#CAPABILITY_SELF_MANAGED.
    • 이 녀석은 앱이 해당 call 을 report 하면서 스스로 UI 도 제공한다는 것을 의미함.
  • unique id 를 가진 PhoneAccount. 이 id 에는 PII(Personally Identifiable Information) 를 비롯한 민감 정보를 담아서는 안 된다. 일반적으로 사용되는 값은 UUID 이다.

TelecomManager#registerPhoneAccount(PhoneAccount)를 통해 Telecom 에 PhoneAccount 를 등록해야 한다.
PhoneAccount 는 재부팅 기반으로 유효하다.
TelecomManager#getOwnSelfManagedPhoneAccounts() 를 통해 앱이 등록한 PhoneAccount 를 확인할 수 있다.
앱은 일반적으로 한개의 PhoneAccount 만을 등록해야 한다.

 

 

Implementing ConnectionService

#
TelecomManager#placeCall(Uri, Bundle) 을 통해 새로운 발신 통화를 시작하거나, TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle) 을 통해 새로운 수신 전화를 알릴 수 있다.
이 API 들을 호출하는 것은 Telecom stack 에 앱의 ConnectionService 를 bind 시킨다.
Telecom 은 앱에 해당 call 요청을 받아들일 수 없다고 하거나, (진행중인 긴급통화가 있는 경우) 앱 안의 Call 을 의미하는 Connection 을 요청할 수도 있다.
앱은 다음 ConnectionService 함수들을 구현해야만 한다.

  • ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) : Telecom 에 의해 호출되며, TelecomManager#placeCall(Uri, Bundle) 에 해당하는 Connection 을 새로 만들어 전달해야 한다.
  • ConnectionService#onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest) : Telecom 에 의해 호출되며, TelecomManager#placeCall(Uri, Bundle) 에 해당하는 call 이 현재 다뤄질 수 없음을 의미한다.
  • ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) : Telecom 에 의해 호출되며 TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle) 에 매칭되는 Connection 을 생성해 전달해야 한다.
  • ConnectionService#onCreateIncomingConnectionFailed((PhoneAccountHandle, ConnectionRequest) : Telecom 에 의해 호출되며 TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle) 에 의한 call 요청이 현재 다뤄질 수 없음을 의미한다.

 

 

Implementing a Connection

#
앱은 Connection 을 extends 하여 앱의 call 을 표현해야 한다.
Connection 을 새로 생성한 경우, Connection 에 다음 property 들이 설정되어야 한다.

  • Connection#setAddress(Uri, int) : 상대방을 표시. 예시로 Uri 에는 PhoneAccount#SCHEME_TEL 과 phoneNumber 가 표시될 수 있다.
  • Connection#setCallerDisplayName(String ,int) : 상대방의 표시 이름. Bluetooth, wearable 등에 표시될 정보. 전화번호 외의 정보가 표시될 때 유용한다.
  • Connection#setConnectionProperties(int) : Connection#PROPERTY_SELF_MANAGED 해당 call 이 앱에 의해 handle 됨을 나타내기 위해 반드시 설정한다.
  • Connection#setConnectionCapabilities(int) : 앱이 hold call 등의 call 의 inactive 상태를 지원한다면, Connection#CAPABILITY_SUPPORT_HOLD & Connection#CAPABILITY_HOLD 를 설정해주어야 한다.
  • Connection#setAudioModeIsVoip(boolean) : VoIp call 임을 알리기 위해 true 를 설정할 수 있다.
  • 새롭게 생성되는 Connection 객체에 대해 Connection#setActive(), Connection#setOnHold() 등을 호출하면 안 된다. ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) 또는 ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) 을 통해 Telecom 에 call 이 add 될때까지

 

 

How to Place Outgoing Calls

#
TelecomManager#placeCall(Uri, Bundle) 을 통해 발신을 할 수 있다.
Uri 를 통해 수신자를 명시하고, Bundle 에 TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE key 에 대해 PhoneAccountHandle 을 명시한다.

Telecom 은 앱의 ConnectionService 에 bind 하고 아래 callback 중 하나를 불러준다.

  • ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) : Connection 을 제공해야 한다.
  • ConnectionService#onCreateOutgoingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

새로운 발신 전화는 Connection#STATE_DIALING state 로 시작한다.
이 상태는 상대방에게 전화를 연결하는 단계를 이야기한다.
상대방이 전화를 받으면, Connection#setActive() 를 호출해주어 Telecom 이 active 상태를 알 수 있도록 해야 한다.

 

 

How to Add Incoming Calls

#
수신전화를 받으면 TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle) 을 호출해주어야 한다.
Telecom 은 ConnectionService 에 bind 하고, 아래 callback 중 하나를 불러준다.

  • ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest) : Connection 을 전달해주어야 한다.
  • ConnectionService#onCreateIncomingConnectionFailed(PhoneAccountHandle, ConnectionRequest)

새로운 수신전화는 Connection#STATE_RINGING 상태로 시작한다.
새로운 수신전화가 수신을 기다리는 상태를 의미한다.
Telecom 은 rington 을 재생하지 않고, notification 을 post 하지도 않는다. 앱이 다뤄야 할 일이다.
Telecom 은 Connection#onShowIncomingCallUi() 를 호출해준다. 이때가 noti 를 등록할 때이다.

Incoming call noti (or full screen UI) 는 보통 "수신", "거절" action 을 제공한다.
action 은 PendingIntent 를 통하며, 수신시에는 Connection#setActive(), 거절시에는 Connection#setDisconnected(DisconnectCause)를 호출해주어 Telecom 에 call 상태를 알려야 한다.
거절시에는 DisconnectCause#REJECTED 를 전달하고, Connection#destroy() 를 호출해준다.

수신을 대비하여 앱은 Connection#onAnswer(int) 와 Connection#onAnswer() 을 구현해야 한다.
이는 user 가 Bluetooth 나 wearable 등의 다른 장비의 UI 를 통해 통화를 수신하는 경우 불린다.
이 때 Connection#setActive() 를 호출해주어야 한다.

앱은 Connection#onReject() 도 구현해야 하며, 이 역시 Bluetooth 나 wearable 등의 다른 장비 UI 를 통해 거절하는 경우 불린다.
이 때 Connection#setDisconnected(DisconectCause) 를 호출해주어야 한다.

 

 

Ending Calls

#
전화가 종료되면, Telecom 에 종료를 알릴 책임이 있다.

Connection#setDisconnected(DisconnectCause) : call 이 종료됨을 알린다. DisconnectCause#LOCAL 또는 DisconnectCause#REMOTE 를 전달한다.
Connection#destroy() : Telecom 에 call 객체가 정리되어도 됨을 알린다. call 종료시 항상 불러주어야 한다.

수신전화를 받을 때와 마찬가지로, 앱 바깥쪽에서 call 을 종료시킬 수 있다.
이는 Connection#onDisconnect() 를 구현하여 대응해야 하며, 여기서 Connection#setDisconnected(DisconnectCause) 를 호출해야 한다. 이때 DisconnectCause#LOCAL 이어야 한다.

 

 

Holding and Unholding Calls

#
Connection#CAPABILITY_SUPPORT_HOLD 와 Connection#CAPABILITY_HOLD 를 명시한 경우, Telecom 은 call 이 suspend 상태에 들어갈 수 있음을 안다.
앱이 통화 대기를 지원하면 user 에게 우리 call, 다른 앱의 call, 그리고 ims call 간의 switch call 기회를 제공할 수 있다.
앱이 통화 대기를 지원하지 않으면, user 가 다른 앱의 전화를 수신하거나, IMS 전화를 수신했을 때 Telecom 으로부터 통화 종료를 요청받을 수 있다.

 

#
Connection#setOnHold(), Connection#setActive() 를 통해 대기와 활성화를 컨트롤 할 수 있다.

 

#
Telecom 으로부터 Connection#onHold(), Connection#onUnhold() 를 요청받을 수 있다.
Bluetooth 나 다른 앱 등에서 전화를 받는 경우 해당 함수들이 불릴 수 있다.
onHold() 가 불렸을 때 Connection#setOnHold() 를 불러주어야 하고, onUnHold() 가 불렸을 때는 반드시 Connection#setActive() 를 불러주어야 한다.

 

 

반응형

댓글