Telekommunikation

Mit der neuen Android Telecom Jetpack-Bibliothek kann der Plattform ganz einfach mitgeteilt werden, in welchem Status sich Ihr Anruf befindet. Den Quellcode und eine Beispiel-App finden Sie auf GitHub.

Abhängigkeiten und Berechtigungen

Öffnen Sie zuerst die Datei „build.gradle“ des App-Moduls und fügen Sie eine Abhängigkeit für das Androidx Telecom-Modul hinzu:

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

Geben Sie in Ihrem App-Manifest an, dass Ihre App die Berechtigung MANAGE_OWN_CALLS verwendet:

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

Anwendung registrieren

Um Android über Ihre App zu informieren, müssen Sie sie und ihre Funktionen registrieren. Dadurch weiß Android, welche Funktionen deine App unterstützt, z. B. Videoanrufe, Anrufstreaming und das Halten von Anrufen. Diese Informationen sind wichtig, damit Android sich selbst konfigurieren kann, damit es mit den App-Funktionen funktioniert.

 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)

Plattformintegration

Die beiden häufigsten Anrufszenarien für eine aufrufende Anwendung sind eingehende und ausgehende Anrufe. Verwende die folgenden APIs, um die Richtung des Anrufs korrekt zu registrieren und den Nutzer entsprechend mit Benachrichtigungen zu benachrichtigen.

Anruf registrieren

In diesem Beispiel wird gezeigt, wie ein eingehender Anruf registriert wird:

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

Das callAttributes-Objekt kann die folgenden Eigenschaften haben:

  • displayName: Der Name des Anrufers, der Besprechung oder der Sitzung.
  • address: Die Adresse des Anrufs. Hinweis: Dieser Link kann auf einen Besprechungslink erweitert werden.
  • direction: Die Richtung des Anrufs, z. B. eingehend oder ausgehend.
  • callType: Informationen zu den übertragenen Daten, z. B. Video und Audio.
  • callCapabilities: Ein Objekt, das die Funktionen des Aufrufs angibt.

Das callCapabilities-Objekt kann die folgenden Eigenschaften haben:

  • streaming: gibt an, ob der Aufruf das Audiostreaming auf einem anderen Android-Gerät unterstützt.
  • transfer: Gibt an, ob der Anruf durchgestellt werden kann.
  • hold: Gibt an, ob der Anruf gehalten werden kann.

Anruf hinzufügen

Die Methode addCall() gibt eine Ausnahme zurück, wenn das Gerät keine Telekommunikations unterstützt oder wenn beim Einrichten des Aufrufs ein Fehler aufgetreten ist.

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

Anruf entgegennehmen

Nachdem du einen eingehenden Anruf getätigt hast, musst du ihn annehmen oder ablehnen. Dieses Beispiel zeigt, wie ein Anruf angenommen wird:

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

    }

    is CallControlResult.Error -> {

    }
}

Wenn ein anderer Anruf läuft, gibt answer() den Befehl CallControlResult.Error zurück. Darin wird auch mitgeteilt, warum der Anruf nicht angenommen werden konnte. In diesem Fall muss der Nutzer den anderen Anruf halten.

Anruf ablehnen

Wenn du einen Anruf ablehnen möchtest, trenne den Anruf mit DisconnectCause.Rejected.

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

Ausgehender Anruf

Wenn Sie einen ausgehenden Anruf tätigen, müssen Sie den Aufruf auf active setzen, sobald die Remote-Person einen Anruf annimmt, damit die Plattform erkennt, dass der Anruf läuft:

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

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

Halten eines Anrufs

Wenn deine Anruf-App das Halten von Anrufen unterstützt, teile ihr über setInActive mit, dass dein Anruf nicht aktiv ist und das Mikrofon und die Kamera von anderen Apps verwendet werden können:

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

    }

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

Verbindung trennen

Wenn Sie einen Anruf trennen möchten, teilen Sie dem Telekommunikationsstack mit, dass die Verbindung getrennt werden soll, indem Sie eine legitime Ursache angeben:

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

Audioroute

Während eines Anrufs wechseln Nutzer manchmal zwischen Geräten wie einem Lautsprecher, einem Kopfhörer oder einem Bluetooth-Gerät. Mit der API availableEndpoints und currentCallEndpoint kannst du eine Liste aller für den Nutzer verfügbaren Geräte mit den aktiven Geräten abrufen.

In diesem Beispiel werden beide Abläufe kombiniert, um ein UI-Objekt zu erstellen, über das dem Nutzer eine Liste von Geräten angezeigt wird und welches Gerät aktiv ist:

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

Wenn du ein aktives Gerät ändern möchtest, verwende das requestEndpointChange mit der CallEndpoint, zu der du wechseln möchtest.

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

Unterstützung im Vordergrund

Die Telecom-Bibliothek bietet Unterstützung im Vordergrund. Diese Bibliothek verwendet ConnectionService für Geräte mit Android 13 und niedriger. Ab Android 14 werden für die korrekte Unterstützung von Diensten im Vordergrund das Mikrofon und die Kamera der foregroundtypes verwendet. Weitere Informationen zu Diensten im Vordergrund

Im Rahmen der Anforderungen im Vordergrund muss die App eine Benachrichtigung senden, damit Nutzer wissen, dass die App im Vordergrund ausgeführt wird.

Damit Ihre Anwendung die Priorität für die Ausführung im Vordergrund erhält, erstellen Sie eine Benachrichtigung, sobald Sie den Aufruf bei der Plattform registrieren. Die Priorität im Vordergrund wird entfernt, wenn Ihre App den Anruf beendet oder Ihre Benachrichtigung nicht mehr gültig ist.

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

Oberflächen-Support

Uhren haben eine generische Endpunktempfängeranwendung. Diese Anwendung bietet dem Nutzer eine einfache Benutzeroberfläche, mit der er Anrufe annehmen, ablehnen und trennen kann. Die App unterstützt diese Aktionen durch die Implementierung von Lambda-Funktionen, die der Plattform mitteilen, dass Sie die Aktion auf dem Gerät ausgeführt haben.

Wenn die Anwendung nicht antwortet, führt jede Lambda-Funktion nach 5 Sekunden mit einer fehlgeschlagenen Transaktion aus.

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 = {}