Telecom

Nowa biblioteka Androida Telecom Jetpack ułatwia określenie, w jakim stanie jest Twoje połączenie. Kod źródłowy i przykładową aplikację znajdziesz na GitHubie.

Zależności i uprawnienia

Najpierw otwórz plik build.gradle modułu aplikacji i dodaj zależność do modułu androidx Telecom:

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

W manifeście aplikacji zadeklaruj, że używa ona uprawnienia MANAGE_OWN_CALLS:

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

Zarejestruj aplikację

Aby poinformować Androida o swojej aplikacji, musisz ją zarejestrować i jej funkcje. Dzięki temu dowiesz się, jakie funkcje obsługuje Twoja aplikacja, np. rozmowy wideo, strumieniowanie połączeń i wstrzymywanie połączeń. Te informacje są ważne, ponieważ Android może skonfigurować samodzielnie działanie z funkcjami aplikacji.

 private val callsManager = CallsManager(context)

var capabilities: @CallsManager.Companion.Capability Int =
    CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING

callsManager.registerAppWithTelecom(capabilities)

Integracja platformy

2 najczęstsze sposoby nawiązywania połączeń w każdej aplikacji do wykonywania połączeń to połączenia przychodzące i wychodzące. Aby prawidłowo zarejestrować kierunek wywołania i odpowiednio powiadomić użytkownika za pomocą powiadomień, użyj poniższych interfejsów API.

Rejestrowanie połączenia

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

companion object {
  const val APP_SCHEME = "MyCustomScheme"
  const val ALL_CALL_CAPABILITIES = (CallAttributes.SUPPORTS_SET_INACTIVE
    or CallAttributes.SUPPORTS_STREAM or CallAttributes.SUPPORTS_TRANSFER)

  const val INCOMING_NAME = "Luke"
  val INCOMING_URI: Uri = Uri.fromParts(APP_SCHEME, "", "")
  // Define all possible properties for CallAttributes
  val INCOMING_CALL_ATTRIBUTES =
    CallAttributes(
      INCOMING_NAME,
      INCOMING_URI,
      DIRECTION_INCOMING,
      CALL_TYPE_VIDEO_CALL,
      ALL_CALL_CAPABILITIES)
}

Obiekt callAttributes może mieć te właściwości:

  • displayName: nazwa rozmówcy, spotkania lub sesji.
  • address: adres połączenia. Uwaga: tę opcję można rozwinąć do linku do spotkania.
  • direction: kierunek połączenia, np. przychodzące lub wychodzące.
  • callType: informacje związane z przesyłanymi danymi, np. wideo i dźwięk.
  • callCapabilities: obiekt określający możliwości wywołania.

Obiekt callCapabilities może mieć te właściwości:

  • streaming: wskazuje, czy połączenie obsługuje strumieniowe przesyłanie dźwięku na inne urządzenie z Androidem.
  • transfer: wskazuje, czy połączenie można przekazać.
  • hold: wskazuje, czy połączenie może być zawieszone.

Dodaj rozmowę

Metoda addCall() zwraca wyjątek, jeśli urządzenie nie obsługuje połączeń telekomunikacyjnych lub jeśli podczas konfigurowania połączenia wystąpił błąd.

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onIsCallAnswered, // Watch needs to know if it can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
        callControlScope = this
    }
}

Odbieranie połączenia

Po nawiązaniu połączenia przychodzącego musisz je odebrać lub odrzucić. Ten przykład pokazuje, jak odebrać połączenie:

when (answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {

    }
}

Jeśli trwa inne połączenie, answer() zwróci wartość CallControlResult.Error, która informuje, dlaczego nie udało się odebrać połączenia. W takim przypadku użytkownik musi zawiesić drugie połączenie.

Odrzucanie połączenia

Aby odrzucić połączenie, rozłącz je, używając numeru DisconnectCause.Rejected.

fun onRejectCall(){
    coroutineScope.launch {
        callControlScope?.let {
            it.disconnect(DisconnectCause(DisconnectCause.REJECTED))
        }
    }
}

Połączenie wychodzące

Gdy podczas nawiązywania połączenia wychodzącego rozmówca odbierze połączenie, musisz ustawić wywołanie na active, aby poinformować platformę, że połączenie jest w toku:

when (setActive()) {
    is CallControlResult.Success -> {
        onIsCallActive()
    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Przełączanie połączenia w stan oczekiwania

Jeśli aplikacja do połączeń obsługuje wstrzymywanie połączeń, użyj setInActive, aby poinformować platformę, że połączenie nie jest aktywne, a mikrofonu i kamery mogą być używane przez inne aplikacje:

when (setInActive()) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Rozłącz

Aby rozłączyć połączenie, poinformuj zespół telekomunikacyjny o rozłączeniu, podając prawidłową przyczynę:

coroutineScope.launch {
    callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL))
}

Kieruj dźwięk

Podczas połączenia użytkownicy czasami przełączają się między urządzeniami, takimi jak głośnik, słuchawka czy urządzenie Bluetooth. Za pomocą interfejsów API availableEndpoints i currentCallEndpoint pobierz listę wszystkich urządzeń dostępnych dla użytkownika oraz urządzeń, które są aktywne.

W tym przykładzie połączono oba przepływy, aby utworzyć obiekt interfejsu pokazujący użytkownikowi listę urządzeń i listę urządzeń, które jest aktywne:

availableEndpoint = combine(callControlScope.availableEndpoints,
    callControlScope.currentCallEndpoint) {
    availableDevices: List<CallEndpoint>, activeDevice : CallEndpoint ->
    availableDevices.map {
        EndPointUI(
            isActive = activeDevice.endpointName == it.endpointName, it
        )
    }
}

Aby zmienić aktywne urządzenie, użyj requestEndpointChange i CallEndpoint, na które chcesz się przełączyć.

coroutineScope.launch {
     callControlScope?.requestEndpointChange(callEndpoint)
}

Wsparcie na pierwszym planie

Biblioteka Telecom jest dostępna w ramach działania na pierwszym planie. Ta biblioteka używa funkcji ConnectionService na urządzeniach z Androidem 13 lub starszym. W Androidzie 14 i nowszych ten model korzysta z mikrofonu i aparatu pierwszego poziomu, aby prawidłowo obsługiwać usługi na pierwszym planie. Więcej informacji o usługach działających na pierwszym planie

W ramach wymagań dotyczących działania na pierwszym planie aplikacja musi opublikować powiadomienie informujące użytkowników, że działa na pierwszym planie.

Aby mieć pewność, że aplikacja otrzyma priorytet wykonywania na pierwszym planie, utwórz powiadomienie po zarejestrowaniu wywołania na platformie. Priorytet jest usuwany, gdy aplikacja zakończy wywołanie lub powiadomienie przestanie być ważne.

is TelecomCall.Registered -> {
    val notification = createNotification(call)
    notificationManager.notify(TELECOM_NOTIFICATION_ID, notification)
}

Obsługa powierzchni

Zegarki mają ogólną aplikację odbiornika punktu końcowego. Aplikacja ta udostępnia użytkownikowi podstawowy interfejs, taki jak odbieranie, odrzucanie i rozłączanie połączeń. Aplikacja obsługuje te działania przez implementowanie funkcji lambda informujących platformę o wykonaniu działania na urządzeniu.

Każda funkcja lambda wyłącza się po 5 sekundach z nieudaną transakcją, jeśli aplikacja nie odpowiada.

callsManager.addCall(
        attributes,
        onIsCallAnswered, // Watch/Auto need to know if they can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
//Call Scope
}
/**
  *  Can the call be successfully answered??
  *  TIP: Check the connection/call state to see if you can answer a call
  *  Example you may need to wait for another call to hold.
  **/
val onIsCallAnswered: suspend(type: Int) -> Unit = {}

/**
  * Can the call perform a disconnect
  */
val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {}

/**
  *  Check is see if you can make the call active.
  *  Other calls and state might stop us from activating the call
  */
val onIsCallActive: suspend () -> Unit = {
    updateCurrentCall {
    }
}

/**
  * Check to see if you can make the call inactivate
  */
val onIsCallInactive: suspend () -> Unit = {}