Core-Telecom

Biblioteka Core-Telecom upraszcza proces integrowania aplikacji do wykonywania połączeń z platformą Android, udostępniając niezawodny i spójny zestaw interfejsów API.

Jeśli chcesz poznać praktyczne implementacje, przykładowe aplikacje znajdziesz na GitHubie:

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 aplikacji

Zarejestruj aplikację do wykonywania 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 obrazu):

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żyj interfejsów API Core-Telecom, aby tworzyć cykl życia połączenia i nim zarządzać.

Tworzenie połączenia

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: audio lub wideo.
  • callCapabilities: obsługuje przekazywanie i wstrzymywanie.

Ten przykład pokazuje, jak utworzyć połączenie przychodzące:

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żyj callsManager.addCall z CallAttributesCompat i wywołaniami zwrotnymi, aby dodać nowe połączenie do systemu i zarządzać aktualizacjami zdalnego interfejsu. callControlScope w bloku addCall umożliwia przede wszystkim aplikacji zmianę stanu połączenia i odbieranie 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 CallControlScope:

when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> { /* Call answered */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Odrzucanie połączenia

Odrzuć połączenie za pomocą disconnect() z DisconnectCause.REJECTED w CallControlScope:

disconnect(DisconnectCause(DisconnectCause.REJECTED))

Aktywowanie połączenia wychodzącego

Ustaw połączenie wychodzące jako aktywne, gdy odbiorca odbierze połączenie:

when (val result = setActive()) {
    is CallControlResult.Success -> { /* Call active */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Przełączanie połączenia w stan oczekiwania

Użyj setInactive(), aby przełączyć połączenie w stan oczekiwania:

when (val result = setInactive()) {
    is CallControlResult.Success -> { /* Call on hold */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Rozłączanie połączenia

Rozłącz połączenie za pomocą 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ą requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

Obsługa na pierwszym planie

Biblioteka używa ConnectionService (Android 13, poziom API 33 i niższy) lub foregroundtypes (Android 14, poziom API 34 i wyższy) do obsługi na pierwszym planie.

Zgodnie z wymaganiami dotyczącymi pierwszego planu aplikacja musi wysyłać powiadomienia, aby użytkownicy wiedzieli, że aplikacja działa na pierwszym planie.

Aby mieć pewność, że aplikacja ma priorytet wykonywania na pierwszym planie, utwórz powiadomienie po dodaniu połączenia do platformy. Priorytet na pierwszym planie jest usuwany, gdy aplikacja kończy połączenie lub powiadomienie traci ważność.

Więcej informacji o usługach działających na pierwszym planie.

Obsługa zdalnego interfejsu

Urządzenia zdalne (smartwatche, zestawy słuchawkowe Bluetooth, Android Auto) mogą zarządzać połączeniami bez bezpośredniej interakcji z telefonem. Aplikacja musi implementować lambdy wywołań zwrotnych (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) przekazywane do CallsManager.addCall, aby obsługiwać działania inicjowane przez te urządzenia.

Gdy wystąpi działanie zdalne, zostanie wywołana odpowiednia lambda.

Pomyślne wykonanie lambdy sygnalizuje, że polecenie zostało przetworzone. Jeśli polecenia nie można wykonać, lambda powinna zgłosić wyjątek.

Prawidłowa implementacja zapewnia płynne sterowanie połączeniami na różnych urządzeniach. Dokładnie przetestuj aplikację na różnych zdalnych interfejsach.

Rozszerzenia połączeń

Oprócz zarządzania stanem połączenia i trasą audio połączeń biblioteka obsługuje też rozszerzenia połączeń, czyli opcjonalne funkcje, które aplikacja może zaimplementować, aby zapewnić lepsze wrażenia z połączeń na zdalnych interfejsach, takich jak Android Auto. Te funkcje obejmują 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 też obsługują wyświetlanie tych rozszerzeń w interfejsie. Oznacza to, że te funkcje będą też dostępne na urządzeniach zdalnych, z których użytkownicy będą mogli korzystać.

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. Dodatkowo ten zakres udostępnia dodatkową metodę onCall, która po zakończeniu wymiany możliwości rozszerzenia i inicjowania udostępnia aplikacji 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...
        }
    }
}

Obsługa uczestników połączenia

Jeśli aplikacja obsługuje uczestników połączenia w przypadku spotkań lub połączeń grupowych, użyj addParticipantExtension, aby zadeklarować obsługę tego rozszerzenia, i użyj powiązanych interfejsów API, aby aktualizować zdalne interfejsy, gdy uczestnicy się zmieniają.

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 informowania zdalnych interfejsów o tym, którzy uczestnicy są w połączeniu, można też aktualizować aktywnego uczestnika za pomocą ParticipantExtension#updateActiveParticipant.

Obsługiwane są też opcjonalne działania związane z uczestnikami połączenia. Aplikacja może użyć ParticipantExtension#addRaiseHandSupport, aby obsługiwać podnoszenie rąk przez uczestników podczas połączenia i sprawdzać, którzy inni uczestnicy też 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)
        }
    }

Obsługa wyciszania połączeń

Wyciszanie 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 na poziomie połączenia, więc Jetpack zajmuje się złożonością zarządzania globalnym stanem wyciszenia trwających połączeń komórkowych, gdy aktywne jest połączenie VOIP. Dzięki temu wyciszanie wychodzącego dźwięku jest mniej podatne na błędy w scenariuszach z wieloma połączeniami, a także umożliwia korzystanie z przydatnych funkcji, takich jak wskazówki „czy mówisz”, gdy użytkownik mówi, nie zdając sobie sprawy, że włączone jest wyciszanie połączeń.

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

Obsługa ikon połączeń

Ikona połączenia umożliwia aplikacji określenie niestandardowej ikony reprezentującej połączenie, która ma być wyświetlana na zdalnych interfejsach podczas połączenia. Tę ikonę można też aktualizować przez cały czas trwania 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)
        }
    }

Dodawanie do systemowego rejestru połączeń

Możesz dodać połączenia VoIP aplikacji do systemowego rejestru połączeń, aby były widoczne w systemowym dialerze i użytkownicy mogli z niego oddzwaniać. Więcej informacji znajdziesz w artykule Ujednolicona historia połączeń.