Biblioteka Core-Telecom upraszcza proces integrowania aplikacji do dzwonienia z platformą Android, udostępniając solidny i spójny zestaw interfejsów API.
Jeśli chcesz poznać praktyczne zastosowania, przykładowe aplikacje znajdziesz na GitHubie:
- Lekka aplikacja przykładowa – minimalny przykład pokazujący użycie interfejsu
Core-TelecomAPI. Idealne do szybkiego zrozumienia podstawowych pojęć. - Kompleksowa aplikacja przykładowa (opracowana przez zespół Core-Telecom) – aplikacja z większą liczbą funkcji, która prezentuje zaawansowane funkcje telekomunikacyjne i sprawdzone metody. To świetne źródło informacji o złożonych scenariuszach integracji.
Konfigurowanie Core-Telecom
Dodaj zależność androidx.core:core-telecom do pliku build.gradle aplikacji:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0")
}
Zadeklaruj uprawnienie MANAGE_OWN_CALLS w pliku AndroidManifest.xml:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Rejestracja swojej aplikacji
Zarejestruj aplikację do połączeń w Androidzie za pomocą CallsManager, aby zacząć dodawać połączenia do systemu. Podczas rejestracji określ możliwości aplikacji (np. obsługę dźwięku i wideo):
val callsManager = CallsManager(context)
val capabilities: @CallsManager.Companion.Capability Int =
(CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)
callsManager.registerAppWithTelecom(capabilities)
Zarządzanie połączeniami
Używaj interfejsów Core-Telecom API do tworzenia cyklu życia połączenia i zarządzania nim.
Utwórz połączenie
Obiekt CallAttributesCompat określa właściwości unikalnego połączenia, które może mieć te cechy:
displayName: nazwa dzwoniącego.address: adres połączenia (np. numer telefonu, link do spotkania);direction: przychodzące lub wychodzące.callType: dźwięk lub wideo.callCapabilities: obsługuje przekazywanie i zawieszanie połączeń.
Oto przykład tworzenia połączenia przychodzącego:
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
)
}
Dodawanie połączenia
Używaj callsManager.addCall z CallAttributesCompat i wywołaniami zwrotnymi, aby dodać nowe połączenie do systemu i zarządzać zdalnymi aktualizacjami powierzchni. callControlScope w bloku addCall umożliwia przede wszystkim zmianę stanu połączenia i otrzymywanie aktualizacji dźwięku:
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.
}
Odbieranie połączenia
Odbierz połączenie przychodzące w ciągu CallControlScope:
when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
is CallControlResult.Success -> { /* Call answered */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Odrzucanie połączenia
Odrzucanie połączenia za pomocą aplikacji disconnect() z DisconnectCause.REJECTED w CallControlScope:
disconnect(DisconnectCause(DisconnectCause.REJECTED))
Aktywowanie połączenia wychodzącego
Ustawianie połączenia wychodzącego jako aktywnego po odebraniu przez drugą stronę:
when (val result = setActive()) {
is CallControlResult.Success -> { /* Call active */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Przełączanie połączenia w stan oczekiwania
Użyj numeru setInactive(), aby wstrzymać połączenie:
when (val result = setInactive()) {
is CallControlResult.Success -> { /* Call on hold */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Rozłączanie połączenia
Aby rozłączyć połączenie za pomocą urządzenia disconnect() z DisconnectCause:
disconnect(DisconnectCause(DisconnectCause.LOCAL))
Zarządzanie punktami końcowymi audio połączeń
Obserwuj punkty końcowe audio i zarządzaj nimi za pomocą currentCallEndpoint,availableEndpoints i isMuted Flow w CallControlScope.
fun observeAudioStateChanges(callControlScope: CallControlScope) {
with(callControlScope) {
launch { currentCallEndpoint.collect { /* Update UI */ } }
launch { availableEndpoints.collect { /* Update UI */ } }
launch { isMuted.collect { /* Handle mute state */ } }
}
}
Zmień aktywne urządzenie audio za pomocą ikony requestEndpointChange():
coroutineScope.launch {
callControlScope.requestEndpointChange(callEndpoint)
}
Obsługa pierwszego planu
Biblioteka używa ConnectionService (Android 13, API na poziomie 33 i niższym) lub foregroundtypes (Android 14, API na poziomie 34 i wyższym) do obsługi działania na pierwszym planie.
W ramach wymagań dotyczących pierwszego planu aplikacja musi wyświetlać powiadomienie, aby użytkownicy wiedzieli, że jest ona uruchomiona na pierwszym planie.
Aby mieć pewność, że Twoja aplikacja będzie miała priorytet wykonywania na pierwszym planie, utwórz powiadomienie po dodaniu połączenia z platformą. Priorytet pierwszego planu jest usuwany, gdy aplikacja kończy połączenie lub powiadomienie nie jest już ważne.
Więcej informacji o usługach na pierwszym planie
Pomoc zdalna dotycząca urządzeń Surface
Urządzenia zdalne (smartwatche, słuchawki Bluetooth, Android Auto) mogą zarządzać połączeniami bez bezpośredniej interakcji z telefonem. Aplikacja musi implementować wywołania zwrotne lambda (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) przekazywane do CallsManager.addCall w celu obsługi działań inicjowanych przez te urządzenia.
Gdy wystąpi działanie zdalne, wywoływana jest odpowiednia funkcja lambda.
Pomyślne zakończenie funkcji Lambda oznacza, że polecenie zostało przetworzone. Jeśli polecenia nie można wykonać, funkcja lambda powinna zgłosić wyjątek.
Prawidłowe wdrożenie zapewnia płynne sterowanie połączeniami na różnych urządzeniach. Dokładnie przetestuj urządzenie na różnych powierzchniach.
Rozszerzenia połączeń
Oprócz zarządzania stanem połączenia i ścieżką dźwięku biblioteka obsługuje też rozszerzenia połączeń, czyli funkcje opcjonalne, które aplikacja może wdrożyć, aby zapewnić lepsze wrażenia podczas połączeń na zdalnych platformach, takich jak Android Auto. Obejmują one sale konferencyjne, wyciszanie połączeń i dodatkowe ikony połączeń. Gdy aplikacja zaimplementuje rozszerzenie, informacje, które udostępnia, będą synchronizowane ze wszystkimi połączonymi urządzeniami, które również obsługują wyświetlanie tych rozszerzeń w interfejsie. Oznacza to, że te funkcje będą dostępne również na urządzeniach zdalnych, z którymi użytkownicy będą mogli wchodzić w interakcje.
Tworzenie połączenia z rozszerzeniami
Podczas tworzenia połączenia zamiast używać CallManager#addCall, możesz użyć CallManager#addCallWithExtensions, co daje aplikacji dostęp do innego zakresu o nazwie ExtensionInitializationScope. Ten zakres umożliwia aplikacji zainicjowanie zestawu opcjonalnych rozszerzeń, które obsługuje. Ten zakres udostępnia też dodatkową metodę onCall, która po zakończeniu wymiany i inicjowania funkcji rozszerzenia zwraca do aplikacji obiekt CallControlScope.
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...
}
}
}
Uczestnicy połączenia z zespołem pomocy
Jeśli Twoja aplikacja obsługuje uczestników połączeń w przypadku spotkań lub połączeń grupowych, użyj elementu addParticipantExtension, aby zadeklarować obsługę tego rozszerzenia, i powiązanych interfejsów API, aby aktualizować urządzenia zdalne, gdy zmieniają się uczestnicy.
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)
}
}
Oprócz powiadamiania zdalnych urządzeń o tym, którzy uczestnicy są w rozmowie, aktywnego uczestnika można też zaktualizować za pomocą ParticipantExtension#updateActiveParticipant.
Obsługiwane są też opcjonalne działania związane z uczestnikami połączenia.
Aplikacja może używać ParticipantExtension#addRaiseHandSupport, aby obsługiwać funkcję podnoszenia ręki przez uczestników połączenia i sprawdzać, którzy inni uczestnicy również podnieśli ręce.
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)
}
}
Wyciszanie połączeń z zespołem pomocy
Funkcja wyciszania połączeń umożliwia użytkownikowi wyciszenie wychodzącego dźwięku połączenia bez fizycznego wyciszania mikrofonu urządzenia. Ta funkcja jest zarządzana w przypadku każdego połączenia, więc Jetpack obsługuje złożoność zarządzania globalnym stanem wyciszenia trwających połączeń komórkowych, gdy aktywne jest połączenie VoIP. Dzięki temu wyciszanie dźwięku wychodzącego jest mniej podatne na błędy w przypadku wielu połączeń, a także umożliwia korzystanie z przydatnych funkcji, takich jak wskazanie „czy mówisz”, gdy użytkownik mówi, nie zdając sobie sprawy, że wyciszenie połączenia jest włączone.
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)
}
}
Ikony połączeń z zespołem pomocy
Ikona połączenia umożliwia aplikacji określenie niestandardowej ikony reprezentującej połączenie, która będzie wyświetlana na urządzeniach zdalnych podczas połączenia. Ikona może się też zmieniać w trakcie połączenia.
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)
}
}
Integracja z dziennikiem połączeń systemowych
Core-Telecom umożliwia aplikacji integrację z systemowym rejestrem połączeń. Ta funkcja umożliwia wyświetlanie połączeń wykonanych lub odebranych za pomocą aplikacji VoIP w historii połączeń systemowej aplikacji Telefon, dzięki czemu użytkownicy mogą używać tej aplikacji do inicjowania połączeń zwrotnych.
Włącz integrację z dziennikiem połączeń
Aby włączyć tę funkcję, aplikacja musi zarejestrować się w celu obsługi intencji TelecomManager.ACTION_CALL_BACK w AndroidManifest.xml i udostępnić Activity do przetwarzania wywołania zwrotnego. Załóżmy na przykład, że aktywność, która obsługuje wywołanie zwrotne, to VoipCallbackActivity. Wtedy manifest powinien wyglądać tak:
Zarejestruj intencję w pliku manifestu
<activity
android:name=".VoipCallbackActivity"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.action.CALL_BACK" />
</intent-filter>
</activity>
Obsługa intencji wywołania zwrotnego
Gdy użytkownik zainicjuje oddzwonienie z dialera systemowego, zarejestrowane działanie zostanie uruchomione z działaniem TelecomManager.ACTION_CALL_BACK.
Intencja zawiera wartość TelecomManager.EXTRA_UUID przekazaną do aplikacji, gdy połączenie zostało dodane za pomocą funkcji CallsManager.
Metoda CallControlScope getCallId zwraca ten unikalny identyfikator wywołania.
W aplikacji należy zapisać wszystkie informacje wymagane do rozpoczęcia połączenia zidentyfikowanego przez UUID (np. listę numerów telefonów do rozmowy grupowej), aby można było odtworzyć i ponownie nawiązać połączenie po wywołaniu zwrotnym.
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()
}
}
Rezygnacja z integracji rejestru połączeń
Domyślnie połączenia są rejestrowane w systemowym rejestrze połączeń. Możesz zrezygnować z tej funkcji w przypadku poszczególnych połączeń, ustawiając parametr isLogged na false podczas tworzenia CallAttributesCompat:
val callAttributes = CallAttributesCompat(
displayName = callerName,
address = addressUri,
direction = CallAttributesCompat.DIRECTION_OUTGOING,
isLogExcluded = true // Opt-out of system call logging
)