Core-Telecom

Biblioteka Core-Telecom upraszcza proces integracji aplikacji wywołującej z platformą Android, zapewniając niezawodny i spójny zestaw interfejsów API.

Jeśli chcesz zapoznać się z praktycznymi implementacjami, znajdziesz przykładowe aplikacje na GitHubie:

Konfigurowanie usługi Core-Telecom

Dodaj zależność androidx.core:core-telecom do pliku build.gradle aplikacji:

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0")
}

Deklaruj uprawnienie MANAGE_OWN_CALLS w pliku AndroidManifest.xml:

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

Rejestracja swojej aplikacji

Aby zacząć dodawać połączenia do systemu, zarejestruj aplikację do połączeń w Androidzie, używając CallsManager. Podczas rejestracji określ funkcje aplikacji (np. obsługę dźwięku lub 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 i zarządzania cyklem życia połączeń.

Tworzenie połączenia

Obiekt CallAttributesCompat definiuje właściwości unikalnego połączenia, które może mieć te cechy:

  • displayName: nazwa dzwoniącego.
  • address: adres wywołania (np. numer telefonu, link do spotkania).
  • direction: przychodzące lub wychodzące.
  • callType: dźwięk lub obraz.
  • callCapabilities: obsługuje przenoszenie i blokowanie.

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żyj funkcji callsManager.addCall z opcją CallAttributesCompat i zgłoszeniami zwrotnymi, aby dodać nowe wywołanie do systemu i zarządzać zdalnym aktualizowaniem powierzchni. W bloku addCall funkcja callControlScope umożliwia aplikacji przejście do 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

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

disconnect(DisconnectCause(DisconnectCause.REJECTED))

Aktywowanie połączenia wychodzącego

Aby ustawić połączenie wychodzące jako aktywne, gdy osoba po drugiej stronie odbierze:

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

Przełączanie połączenia w stan oczekiwania

Aby wstrzymać połączenie, naciśnij setInactive():

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

Rozłączanie połączenia

Rozłączanie połączenia za pomocą disconnect() z urządzeniem DisconnectCause:

disconnect(DisconnectCause(DisconnectCause.LOCAL))

Zarządzanie punktami końcowymi dźwięku w połączeniu

Obserwowanie punktów końcowych dźwięku i zarządzanie 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, używając requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

Pomoc na pierwszym planie

Biblioteka używa ConnectionService (Android 13 API na poziomie 33 lub niższym) lub foregroundtypes (Android 14 API na poziomie 34 lub wyższym) do obsługi pierwszego planu.

Zgodnie z wymaganiami dotyczącymi działania na pierwszym planie aplikacja musi wyświetlać powiadomienie, aby użytkownicy wiedzieli, że działa ona na pierwszym planie.

Aby mieć pewność, że Twoja aplikacja będzie miała priorytet w wykonywaniu na pierwszym planie, utwórz powiadomienie po dodaniu wywołania w ramach platformy. Priorytet na pierwszym planie jest usuwany, gdy aplikacja zakończy połączenie lub gdy powiadomienie nie jest już ważne.

Więcej informacji o usługach na pierwszym planie

Pomoc zdalna dla 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) udostępniane do usługi CallsManager.addCall w celu obsługi działań inicjowanych przez te urządzenia.

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

Pomyślne zakończenie działania funkcji lambda sygnalizuje, że polecenie zostało przetworzone. Jeśli polecenie nie może zostać wykonane, funkcja lambda powinna wyrzucić wyjątek.

Właściwe wdrożenie zapewnia płynne sterowanie rozmowami na różnych urządzeniach. Dokładnie przetestuj na różnych powierzchniach.

Rozszerzenia połączeń

Oprócz zarządzania stanem połączeń i trasą audio biblioteka obsługuje też rozszerzenia połączeń, czyli opcjonalne funkcje, które aplikacja może wdrażać, aby zapewnić lepsze wrażenia z rozmów na urządzeniach zdalnych, takich jak Android Auto. Funkcje te obejmują sale konferencyjne, wyciszenie rozmów i dodatkowe ikony rozmów. Gdy aplikacja wdroży rozszerzenie, informacje, które udostępnia, zostaną zsynchronizowane ze wszystkimi połączonymi urządzeniami, które 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ć funkcji CallManager#addCall możesz użyć funkcji CallManager#addCallWithExtensions, która 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 zawiera dodatkową metodę onCall, która zapewnia CallControlScope z powrotem do aplikacji po zakończeniu wymiany i inicjalizacji rozszerzenia.

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 rozmowy z pomocą

Jeśli Twoja aplikacja obsługuje uczestników połączeń na spotkaniach lub w przypadku połączeń grupowych, użyj interfejsu addParticipantExtension, aby zadeklarować obsługę tego rozszerzenia, oraz odpowiednich interfejsów API, aby aktualizować zdalne interfejsy, 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 informowania urządzeń zdalnych o tym, którzy uczestnicy są w połączeniu, aktywny uczestnik może też otrzymywać informacje za pomocą funkcji ParticipantExtension#updateActiveParticipant.

Dostępne są też opcjonalne działania dotyczące uczestników rozmowy. Aplikacja może używać ParticipantExtension#addRaiseHandSupport do obsługi funkcji podnoszenia ręki przez uczestników spotkania i do wyświetlania informacji o tym, 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)
        }
    }

Wyciszenie podczas rozmowy z zespołem pomocy

Funkcja wyciszania rozmów umożliwia użytkownikowi wysłanie prośby do aplikacji o wyciszenie dźwięku podczas połączeń wychodzących bez fizycznego wyciszenia mikrofonu urządzenia. Ta funkcja jest zarządzana w poszczególnych połączeniach, 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 wyciszenie dźwięku podczas połączeń wychodzących jest mniej podatne na błędy w sytuacjach z wieloma połączeniami, a także umożliwia korzystanie z przydatnych funkcji, takich jak sygnał „Czy mówisz?”, gdy użytkownik mówi, nie zdając sobie sprawy, że wyciszenie 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 zdalnych powierzchniach podczas połączenia. Ta ikona może się też zmieniać w trakcie 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)
        }
    }