Apps-Grundlage

Nahtlose Interaktionen zwischen Geräten ermöglichen

Lautsprecher

Info

Über den Vortrag

Da Nutzer zwischen Smartphones, Wearables und Audiogeräten wechseln, ist es für die Interaktion unerlässlich, flüssige geräteübergreifende Erlebnisse zu bieten. Wenn Sie sich jedoch auf Standard-Netzwerkansätze verlassen, führt dies häufig zu einem hohen Akkuverbrauch, Latenzspitzen und fragmentierten Nutzersitzungen.

In diesem Deep Dive werden spezielle Android-APIs für die Kommunikation zwischen mehreren Geräten vorgestellt. Sie bieten speicher- und energieeffiziente Muster, um Übergänge zwischen Geräten sofort, zuverlässig und intuitiv zu gestalten.

Zusammenfassung

  • Der Companion Device Manager ersetzt Dienste im Vordergrund für Bluetooth-Peripheriegeräte, indem er Apps basierend auf der Anwesenheit aktiviert und Verbindungen verwaltet, ohne den Akku zu belasten. Android 17 bietet neue Profile speziell für Medizinprodukte und Fitness-Tracker.
  • Jetpack Telecom fungiert als Systemanrufkoordinator für VoIP-Apps auf verbundenen Geräten wie Smartwatches und Autos und ist ab Android 8 verfügbar. Außerdem werden Stummschaltungs- und Auflegeaktionen über CallControlScope synchronisiert.
  • Zu den kommenden Funktionen von Jetpack Telecom 1.1.0 gehören einheitliche Anruflisten in der Telefon-App des Systems (SDK 36.1+) und automatisches 5G-Network-Slicing in unterstützten Mobilfunknetzen.
  • Nächstes Jahr wird das Quick Share SDK eingeführt, mit dem Entwickler die lokale, Ende-zu-Ende-verschlüsselte Dateifreigabe direkt in Android- und iOS-Apps einbetten können. Mit der Funktion „Zum Teilen aneinanderhalten“ (eingeführt mit dem Pixel 11) kann sogar eine geschlossene Empfänger-App automatisch aktiviert werden.

Ressourcen

Gemini Notebook

Themen

  • geräteübergreifend
  • companion-device-manager
  • jetpack-telecom
  • quick-share
  • Bluetooth
  • voip
  • Peer-to-Peer

Transkript

Einführung in geräteübergreifende Interaktionen

  • Hallo zusammen!

  • Willkommen bei „Nahtlose geräteübergreifende Interaktionen ermöglichen“.

  • Ich bin Hanrui und heute sind meine Kollegen Yacine und Nataraj bei mir.

  • Wenn wir über Konnektivität sprechen, denken wir oft an eine einfache Netzwerkverbindung oder Bluetooth-Headsets.

  • Auf Android-Geräten ist die Konnektivität viel umfassender.

  • Es geht wirklich darum, dass Geräte besser zusammenarbeiten.

  • Heute gehen wir gängige geräteübergreifende Szenarien durch und zeigen Ihnen, welche API für die einzelnen Szenarien geeignet ist.

  • Lange Zeit haben wir Apps nur für ein einziges Gerät entwickelt, nämlich das Smartphone.

  • Dann kamen Wearables auf den Markt.

  • Bald war es ganz normal, eine Smartwatch mit einem Smartphone zu koppeln, um Daten im Hintergrund zu synchronisieren oder Anrufe direkt anzunehmen.

  • Füge Kopfhörer hinzu – füge kabellose Kopfhörer hinzu.

  • Jetzt können Nutzer während eines Anrufs das Audiogerät wechseln – von der Smartwatch zum Smartphone, zu den Kopfhörern oder zum Auto.

  • In letzter Zeit haben viele Nutzer auch ein zweites Gerät mit großem Bildschirm, z. B. ein faltbares Smartphone oder ein Tablet.

  • Sie erwarten, dass die App auf diesen Geräten reibungslos funktioniert.

  • Das gilt auch für Laptops und Chromebooks, auf denen Nutzer Android-Apps ausführen und direkt von ihrem Schreibtisch aus in Verbindung bleiben können.

  • Schon 2023 besaß ein durchschnittlicher Nutzer drei bis vier Geräte.

  • Nutzer erwarten von diesen Geräten mehr als nur eigenständige Funktionen.

  • Sie suchen nach einem außergewöhnlichen Erlebnis, bei dem alle Geräte zusammenarbeiten.

  • Das ist besonders praktisch, wenn man sich wie wir heute im selben Raum befindet und Fotos, Links oder Apps mit den Personen neben sich teilen möchte.

  • Die Zahlen zeigen, dass das Ökosystem riesig ist und schnell wächst.

  • In diesem Jahr gibt es weltweit etwa 22 Milliarden IoT-Geräte, was einem Wachstum von 14 % im Vergleich zum Vorjahr entspricht.

  • Und von all diesen Endpunkten macht WLAN ein Drittel und Bluetooth ein Viertel aus.

  • Geräteübergreifende Konnektivität ist kein Randfall mehr.

  • Sie ist Teil unseres Alltags.

  • Dennoch werden die geräteübergreifenden Funktionen der meisten Apps auch heute noch auf Low-Level-Netzwerk- und Bluetooth-APIs aufgebaut, was drei Herausforderungen mit sich bringt.

  • Zuerst Akku und Arbeitsspeicher.

  • Wenn Hintergrundscans und ‑dienste ausgeführt werden, verbraucht das Akku und Arbeitsspeicher.

  • Zweitens: Latenz.

  • Unvorhersehbare Verzögerungen stören Echtzeitanrufe und die Datensynchronisierung.

  • Und drittens: Verbindungsprobleme.

  • Die Verwaltung von Erkennung, Kopplung und Wiederverbindung auf allen Geräten ist komplex und kann leicht zu einer schlechten Nutzererfahrung führen.

  • Um diese Probleme zu beheben, haben wir spezielle APIs für drei Hauptbereiche entwickelt, mit denen sich geräteübergreifende Funktionen ganz einfach implementieren lassen.

  • Ich beginne mit Companion-Apps, dann spricht Nataraj über VoIP-Apps und später Yacine über P2P-Sharing.

Companion-Apps – Übersicht

  • Sehen wir uns zuerst die Companion-Apps an.

  • Eine Companion-App ist eine App, die Sie auf Ihrem Smartphone installieren, damit ein Companion-Gerät wie eine Smartwatch oder ein Glukosesensor gut damit funktioniert.

  • In diesem Abschnitt geht es um über Bluetooth verbundene Geräte.

  • Es gibt in der Regel vier Kategorien von Companion-Geräten.

  • Erstens: Medizinprodukte wie Glukosesensoren und Vitalfunktionsmonitore.

  • Und Wearables wie Smartwatches und Fitnessarmbänder.

  • Headsets mit schnellem Pairing und intelligenter Steuerung.

  • Und Auto-Kits für Funktionen wie digitale Autoschlüssel und Freisprechanrufe.

  • Jede Kategorie hat ihre eigenen wichtigen User Journeys, aber alle sind auf eine zuverlässige Verbindung zwischen dem Gerät und dem Smartphone angewiesen.

  • Bei Medizinprodukten kann diese Verbindung sogar lebenswichtig sein.

  • Sehen wir uns vier gängige Anforderungen in diesen Kategorien an.

  • Erstens: Geräteverwaltung

  • Die App muss das richtige Gerät unter allen Geräten in der Nähe finden, z. B. die Smartwatch hier, nicht den Kopfhörer oder den Lautsprecher, und dann den Kopplungsvorgang starten, um eine Verbindung herzustellen.

  • Zweitens: Anwesenheitsprüfung.

  • Die App muss wissen, wann das Gerät in der Nähe ist und wann nicht.

  • Und wenn das Gerät auftaucht, muss die App aktiviert werden, damit sie ihre Aufgabe erfüllen kann, auch wenn der Nutzer sie nicht geöffnet hat.

  • Drittens: Verbindungspflege.

  • Sobald eine Verbindung hergestellt ist, soll sie zuverlässig und dauerhaft sein.

  • Wenn die Verbindung unterbrochen wird, weil der Nutzer beispielsweise den Empfangsbereich verlässt und dann zurückkehrt, muss die App die Verbindung automatisch wiederherstellen, auch wenn sie zu diesem Zeitpunkt nicht ausgeführt wird.

  • Viertens: Datensynchronisierung und -übertragung.

  • Hier liegt der eigentliche Wert.

  • Synchronisieren von Sensormesswerten, Senden von Firmware-Updates oder Senden von Einstellungen an das Gerät.

  • Bei Medizinprodukten kann dies entscheidend sein.

  • Ein verpasster Messwert kann eine verpasste Warnung bedeuten.

  • Für alle diese Funktionen muss der App-Prozess aktiv und zum richtigen Zeitpunkt verbunden sein.

  • Wie haben Apps all diese Anforderungen erfüllt?

  • Traditionell waren Apps stark auf Dienste im Vordergrund angewiesen.

  • Hier ist eine typische Implementierung.

  • Wenn Sie schon einmal eine Companion-App entwickelt haben, kommt Ihnen das vielleicht bekannt vor.

  • Sie deklarieren die Berechtigungen für Bluetooth-Scans und ‑Verbindungen, die Berechtigungen für Dienste im Vordergrund und den Typ des Dienstes im Vordergrund für verbundene Geräte sowie die Berechtigung zum Senden von Benachrichtigungen, da ein Dienst im Vordergrund eine dauerhafte Benachrichtigung anzeigen muss.

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
  • Anschließend führen Sie einen Sticky-Dienst aus, um den Anwendungsprozess aufrechtzuerhalten.

    class BleForegroundService : Service() {
       override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          …
          return START_STICKY
       }
    }
  • Das funktioniert zwar, verbraucht aber Akku und Arbeitsspeicher, weil der Dienst ständig ausgeführt wird, auch wenn das Gerät nicht in der Nähe ist.

  • Deshalb haben wir den Companion Device Manager (CDM) entwickelt.

  • Erstens kann sie Ihre App aktivieren, wenn ein Gerät in der Nähe ist. Solange das Gerät in Reichweite bleibt, hält das System den App-Prozess aktiv.

  • Kein Dienst im Vordergrund erforderlich.

  • Zweitens unterstützt CDM Geräteprofile.

  • Mit einem Geräteprofil gewährt der Nutzer alle Berechtigungen, die Ihre App benötigt, mit nur einem Tippen, anstatt eine Reihe von Berechtigungsdialogen durchlaufen zu müssen.

  • Das verbessert die Nutzerfreundlichkeit und die Conversion-Rate erheblich.

  • Das Smartwatch-Profil ist seit Android 12 verfügbar, das Brillen-Profil seit Android 14.

  • In Android 17 haben wir zwei neue Profile speziell für Medizinprodukte und Fitness-Tracker hinzugefügt.

  • Derzeit gibt es auch einige privilegierte Profile, die nur für System-Apps gelten.

  • Sollte sich im Ökosystem ein breiterer Bedarf abzeichnen, werden wir möglicherweise in Erwägung ziehen, sie für mehr Entwickler zu öffnen.

Implementierung mit dem Companion Device Manager

  • Die Verwendung von CDM ist ganz einfach.

  • Nehmen wir als Beispiel ein Medizinprodukt.

  • Zuerst müssen die erforderlichen Berechtigungen deklariert werden: eine für das Medizinprodukt, eine für die Ausführung im Hintergrund und eine für die Erkennung der Anwesenheit des Geräts.

    <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
    <uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
    <uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
  • Im Gegensatz zu den Berechtigungen, die wir gerade bei der Implementierung von Diensten im Vordergrund gesehen haben, sind alle hier aufgeführten Berechtigungen normale Berechtigungen. Das bedeutet, dass sie bei der Installation ohne Aufforderungsdialog gewährt werden.

  • Der Nutzer muss nur die Verknüpfung genehmigen.

  • Sehen wir uns an, wie Sie diese Verknüpfung erstellen.

  • Hier erstellen wir eine Verknüpfungsanfrage mit einem Gerätefilter, damit nur das richtige Gerät angezeigt wird.

    val paringRequest = AssociationRequest.Builder()
       .addDeviceFilter(deviceFilter)
       .setSingleDevice(true)
       .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
       .build()
    
    companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
       object : CompanionDeviceManager.Callback() {
           override fun onAssociationPending(intentSender: IntentSender) {
               intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
           }
    
           override fun onAssociationCreated(associationInfo: AssociationInfo) { … }
    
           override fun onFailure(error: CharSequence?) { … }
       }
    )
  • setSingleDevice auf „true“, da wir nur eine Übereinstimmung möchten, und das Profil auf „medical“.

  • Dann rufen Sie den Mitarbeiter an.

  • Das CDM übernimmt das Scannen.

  • In onAssociationPending erhalten wir einen IntentSender.

  • Starten Sie die App. Das System zeigt ein Dialogfeld für die Verknüpfung an.

  • Bei einem medizinischen Profil wird durch einmaliges Tippen nicht nur die Verknüpfung selbst genehmigt, sondern es werden auch alle Berechtigungen erteilt, die das Profil benötigt. Sogar der Modus „Bitte nicht stören“ wird umgangen, was für wichtige Warnungen entscheidend ist.

  • Sobald der Nutzer auf „Zulassen“ tippt, erhalten wir onAssociationCreated mit AssociationInfo, einschließlich einer Verknüpfungs-ID.

  • Dann können wir die Gerätepräsenz beobachten, indem wir „startObservingDevicePresence“ mit dieser Verknüpfungs-ID aufrufen.

  • Außerdem müssen Sie einen Dienst implementieren, der CompanionDeviceService erweitert.

    class BleCompanionDeviceService : CompanionDeviceService() {
    
       override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
           when (event.event) {
               DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
               DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
           }
       }
    
       private fun onDeviceAppeared(associationId: Int) { … }
    
       private fun onDeviceDisappeared(associationId: Int) { … }
    }
  • Wenn das Gerät in Reichweite ist, wird das System an deinen Dienst gebunden, auch wenn deine App nicht ausgeführt wird.

  • Das System ruft onDevicePresenceEvent mit EVENT_BLE_APPEARED auf.

  • Anschließend kannst du eine Verbindung herstellen und synchronisieren.

  • Wenn das Gerät den Bereich verlässt, erhältst du EVENT_BLE_DISAPPEARED.

  • Anschließend können Sie die Bereinigung durchführen.

  • Kein Always-On-Dienst und keine Benachrichtigung, nur um aktiv zu bleiben.

  • Wir müssen zugeben, dass CDM einige Einschränkungen hat, die Entwickler beachten müssen.

  • Zuerst die Betriebssystemversionen.

  • Die meisten CDM-Funktionen wurden in Android 12 und 13 eingeführt.

  • Aber es gab einige interessante Verbesserungen in Android 16 und die neuen Profile in Android 17.

  • Prüfe daher die Betriebssystemversion zur Laufzeit und halte ein Fallback für ältere Versionen bereit.

  • Die SDK-Zielversion wird jedoch jedes Jahr erhöht und die Mindest-SDK-Version folgt in der Regel.

  • Um die Speicherbelastung zu verringern, der Apps jetzt und in Zukunft ausgesetzt sind, empfehlen wir dringend, so bald wie möglich von Diensten im Vordergrund zu CDM zu migrieren.

  • Wenn Sie einen Anwendungsfall haben, der derzeit nicht von CDM unterstützt wird, lassen Sie es uns wissen.

  • Zweitens unterstützt CDM derzeit das Scannen im Hintergrund, aber nicht das Advertising im Hintergrund.

  • Wir hören selten, dass dies erforderlich ist.

  • Wenn Ihre App jedoch das Smartphone als Peripheriegerät benötigt und kontinuierlich im Hintergrund Werbung schalten muss, würden wir gerne mehr darüber erfahren.

  • Drittens verwendet CDM den Scanmodus mit geringem Stromverbrauch.

  • Dabei wird ein Teil der Echtzeitreaktionsfähigkeit gegen Akku und Arbeitsspeicher eingetauscht.

  • Erwarte also nicht, dass du sofort aufwachst.

  • Schließlich ist kein Einblick in die Nutzlast möglich.

  • Du kannst dich nicht für ein bestimmtes Merkmal oder bestimmte Werbedaten registrieren.

  • Sobald sich das Gerät jedoch in Reichweite befindet, wird die App aktiviert.

  • So kann er eine Verbindung herstellen und die Nutzlast nach Bedarf prüfen.

  • Das ist alles, was Sie über Companion-Apps und CDM wissen müssen.

  • Ich übergebe jetzt an Nataraj, der über VoIP-Apps sprechen wird.

Herausforderungen bei geräteübergreifenden VoIP-Anrufen

  • CompanionDeviceManager ist das richtige Tool, wenn deine App ein dediziertes Peripheriegerät hat und kontinuierlich darüber kommuniziert.

  • Hallo zusammen.

  • Ich bin Nataraj K R.

  • Als Nächstes geht es um das geräteübergreifende Szenario „Anrufe in Echtzeit“.

  • Heutzutage ändert sich die Art und Weise, wie Menschen mit Anrufen umgehen.

  • Mehr als 40 % der Anrufe werden über VoIP-Netzwerke getätigt.

  • Und Android spielt dabei eine wichtige Rolle.

  • Tatsächlich werden 71 % dieser Anrufe auf Android-Geräten getätigt, also fast jeder zweite Anruf.

  • Es geht nicht nur um die Anrufe.

  • Auch das Ökosystem der Geräte, die Nutzer besitzen, wächst rasant.

  • Es gibt über 250 Millionen Autos mit Android Auto auf den Straßen und wir liefern mehr als eine Milliarde Bluetooth-Geräte an Nutzer aus.

  • Kopfhörer, Wear OS-Smartwatches, Android-Autos mit Android Auto und Bluetooth-Lautsprecher sind alles Geräte, die Nutzer neben ihrem Smartphone haben und sie möchten, dass ihre Anrufe nahtlos zwischen all diesen Geräten übertragen werden.

  • Wenn ein Nutzer beispielsweise einen Anruf erhält, kann er einen Blick auf seine Smartwatch werfen, um zu sehen, wer anruft.

  • Sie nehmen den Anruf auf ihrer Smartwatch an.

  • Sie schalten die Stummschaltung nach Bedarf ein oder aus, setzen ihre Kopfhörer auf und setzen den Anruf fort.

  • Wenn sie das Haus verlassen, steigen sie in ihr Auto und erwarten, dass der Anruf nahtlos auf das Auto übertragen wird.

  • Das ist für den Nutzer also zur Routine geworden und keine Premium-Erfahrung.

  • Für Nutzer ist das eine grundlegende Anforderung und es liegt an uns Android-Entwicklern, diese nahtlose Nutzung in unseren VoIP-Apps zu ermöglichen.

  • Wenn Sie jedoch versuchen, das alles selbst von Grund auf neu zu erstellen, werden Sie mit drei großen Problemen konfrontiert.

  • Erstens: Fragmentierung mit Bluetooth-Sockets.

  • Da das System nicht weiß, dass Ihr Anruf einen Anruf verarbeitet, müssten Sie benutzerdefinierte Bluetooth-Sockets und benutzerdefinierte Mediensitzungen erstellen, damit Ihr Anruf auf die Smartwatch oder Android Auto projiziert werden kann.

  • Das zweite Problem ist der Audiofokus.

  • Da Sie mit Low-Level-AudioManager-APIs und Bluetooth-APIs arbeiten müssen, kommt es zu Audiolecks, und das hat für das System keine Priorität, oder?

  • In Ihrer App wird ein VoIP-Anruf geführt. Wenn ein Anruf über den Mobilfunkanbieter eingeht, wird der VoIP-Anruf unterbrochen und das Modem übernimmt die Audioausgabe, sodass Ihre App keinen Ton mehr hat.

  • Und das Schlimmste: Auch wenn der Mobilfunkanruf beendet wird, wird der Audiofokus nicht an Ihre App zurückgegeben.

  • Das dritte Problem ist die Synchronisierung.

  • Wenn der Nutzer auf seiner Smartwatch die Stummschaltung aktiviert, wird der Anruf auf der Benutzeroberfläche des Smartphones weiterhin als nicht stummgeschaltet angezeigt.

  • Das sorgt für große Verwirrung.

  • Der Nutzer beendet den Anruf über die Lenksäule seines Autos, aber auf dem Smartphone wird weiterhin eine Anrufbenachrichtigung angezeigt.

  • Das ist für den Nutzer sehr frustrierend.

Jetzt neu: Jetpack Telecom

  • Um genau diese Probleme zu lösen, haben wir Jetpack Telecom entwickelt.

  • Sie registrieren Ihren Anruf also einmal mit Jetpack Telecom über die CallsManager und Jetpack Telecom fungiert als zentraler Koordinator für Systemaufrufe.

  • Sie ist dafür verantwortlich, Ihren Anruf auf die Smartwatch, Android Auto und andere Remote-Oberflächen zu projizieren, die Sie besitzen.

  • Außerdem wird der Audiofokus verwaltet.

  • Und das Beste daran: All das funktioniert nahtlos, sogar auf Geräten mit Android 8.

  • Die Projektion des Anrufs auf die Smartwatch oder das Autodisplay ist nur die halbe Miete, denn Nutzer möchten den Anruf natürlich auch über diese Geräte steuern.

  • Sie möchten den Anruf über ihre Smartwatch stummschalten oder die Stummschaltung aufheben.

  • Sie möchten den Anruf über die Lenksäule ihres Autos oder durch Doppeltippen auf ihre Kopfhörer beenden.

  • Jetpack Telecom erfasst diese Hardwareereignisse und stellt sie als Callbacks an einem Ort dar, nämlich in CallControlScope.

  • Sie müssen nur auf diese Callbacks warten, die Anruf-Engine verwalten und den Anrufstatus entsprechend aktualisieren.

  • Jetpack Telecom übernimmt die Synchronisierung dieses Anrufstatus zwischen den Geräten.

  • Im Hintergrund bietet die Bibliothek also zwei Arten von Vorteilen.

  • Erstens werden damit natürlich alle Probleme gelöst, über die wir gesprochen haben.

  • Anstatt sich mit Low-Level-AudioManager-APIs zu befassen, bietet die Bibliothek deklarative Aufrufendpunkte, verwaltet den Audiofokus und synchronisiert die Geräte.

  • Sie übernimmt die Übergabe des Anrufs zwischen den Geräten.

  • Und es kümmert sich auch um eingehende Mobilfunkanrufe.

  • Ein weiterer Vorteil der Bibliothek ist, dass sie auf Kotlin basiert. Wenn Sie also einen Anruf mit Jetpack Telecom hinzufügen, wird ein Kotlin-Koroutinenbereich geöffnet.

  • So werden Kotlin-Koroutinen genutzt, die genau bis zum Ende des Aufrufs aktiv bleiben.

  • Alle Jobs, die Sie im Zusammenhang mit dem Anruf ausgeführt haben, werden automatisch bereinigt, sobald der Anruf beendet wird.

  • Es bleiben also keine verwaisten Jobs übrig und der Aufruflebenszyklus wird ebenfalls von der Bibliothek verwaltet.

  • Sie fragen sich jetzt wahrscheinlich, wie Sie diese tolle Bibliothek implementieren können, oder?

  • Und es ist ganz einfach.

  • Schritt 1: Sie müssen die Abhängigkeit deklarieren.

  • Deklarieren Sie anschließend die erforderliche Berechtigung MANAGE_OWN_CALLS und instanziieren Sie ein CallsManager-Objekt.

    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
  • Nachdem das CallsManager-Objekt instanziiert wurde, registrieren Sie Ihre App mit den Funktionen, die Sie mit der App bereitstellen möchten, z. B. Videoanrufe oder Anruf-Streaming.

    // Initialize CallsManager & register supported capabilities once
    val callsManager = CallsManager(context)
    
    callsManager.registerAppWithTelecom(
        capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                       CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
    )
  • Danach folgt Schritt 2.

  • Nachdem Sie Ihre App bei Jetpack Telecom registriert haben, können Sie eingehende und ausgehende Anrufe verarbeiten.

  • Dazu müssen Sie ein CallAttributesCompat-Objekt erstellen, das die Metadaten des Anrufs enthält, z. B. die displayName des Anrufers oder Angerufenen, die Adresse, d. h. den Endpunkt des Anrufers, die Richtung, die angibt, ob es sich um einen eingehenden oder ausgehenden Anruf handelt, damit Jetpack Telecom die entsprechende Benutzeroberfläche auf diesen Remote-Oberflächen anzeigen kann, und dann callType, ob es sich um einen Sprach- oder Videoanruf handelt, damit Sie die erforderlichen Ressourcen erhalten.

    val attributes = CallAttributesCompat(
        displayName = "Kukk Nat",
        address = Uri.parse("tel:+16505550199"),
        direction = CallAttributesCompat.DIRECTION_INCOMING,
        callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
    )
    
    callsManager.addCall(
        attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
    ) {
        // Coroutine CallControlScope active for the duration of the call
    }
  • Sobald das Metadatenobjekt bereit ist, fügen Sie es mit der Methode callsManager.addCall hinzu.

  • Dadurch wird ein Coroutine-Bereich für die Anrufsteuerung geöffnet.

  • Nachdem Sie den Aufruf hinzugefügt haben, folgt Schritt 3.

  • Schritt 3 besteht darin, auf die Aktionen zu warten, die der Nutzer auf diesen Remote-Oberflächen ausführt.

  • Da Sie CallControlScope haben, müssen Sie nur die Kotlin-Flows erfassen.

    callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
        // 1. React to hardware mute toggles from Watch, Car, or Headset
        launch {
            isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
        }
    
        // 2. Observe available endpoints & request declarative switches
        launch {
            currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
        }
        requestEndpointChange(preferredBluetoothEndpoint)
    }
  • Sie erfassen isMuted, um zu reagieren, wenn der Nutzer den Anruf über diese Remote-Oberflächen stummschaltet, und können den Anrufstatus entsprechend aktualisieren.

  • Erfassen Sie currentCallEndpoint, um zu erfahren, auf welchem Endpunkt der Anruf gerade stattfindet, damit Sie Ihre Benutzeroberfläche entsprechend aktualisieren können.

  • Du kannst dieses Gerät auf der Benutzeroberfläche deines Smartphones anzeigen lassen.

  • Wenn der Nutzer von einem Gerät zu einem anderen wechseln möchte, muss er nur die Methode requestEndpointChange mit dem gewünschten Endpunkt aufrufen.

  • So einfach ist das.

  • Mit diesen drei Schritten ist Ihre App einsatzbereit und Jetpack Telecom ist integriert.

  • Aber das ist noch nicht alles.

  • Mit Jetpack Telecom 1.1.0 führen wir viele weitere Funktionen ein, z. B. einheitliche Anruflisten. Das bedeutet, dass alle Ihre VoIP-Anrufe jetzt nativ in der Telefon-App des Systems angezeigt werden.

  • Der Vorteil ist, dass Nutzer einen Rückruf mit nur einmal Tippen direkt über den System-Dialer in Ihrer App starten können.

  • Sie müssen also nicht mehr in allen Apps nach der gewünschten App suchen.

  • Sie können den Rückruf einfach über den System-Dialer starten, den sie gewohnt sind.

  • Aber wir bieten auch detaillierte Einstellungen an.

  • Wenn Sie nicht möchten, dass ein bestimmter Anruf im System protokolliert wird, müssen Sie nur das Flag isLogExcluded im Objekt CallAttributesCompat auf „true“ setzen.

  • Sie müssen nur das Metadatenobjekt, von dem ich vorhin gesprochen habe, auf „true“ setzen. Dann wird Ihr Anruf nicht im System protokolliert und nicht im System-Dialer angezeigt.

  • Die zweite tolle Funktion, die mit 1.1.0 eingeführt wird, ist 5G-Network-Slicing.

  • In unterstützten Mobilfunknetzen stellen die Netzwerke Premium-Netzwerk-Slices bereit. Das bedeutet, dass der Mobilfunkanbieter für einen bestimmten Anwendungsfall einen Netzwerk-Slice mit geringer Latenz und ohne Unterbrechungen bereitstellt, sodass du eine bessere Qualität und ein besseres Erlebnis hast.

  • In unterstützten Mobilfunknetzen ist diese Funktion also sofort verfügbar. Sie müssen nichts weiter tun, nicht einmal eine einzige Zeile Code schreiben.

  • Nachdem Sie die Bibliothek eingebunden haben, werden diese Network Slices für Ihre Aufrufe verwendet. Dadurch profitieren Sie von einer geringeren Latenz und einer besseren Leistung.

  • Wenn Sie diese Funktion aus irgendeinem Grund nicht nutzen möchten, können Sie sie mit dem Flag CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK deaktivieren.

  • Jetpack Telecom wurde bereits von den weltweit führenden Kommunikations-Apps wie Google Meet, WhatsApp, KakaoTalk und Zoho Arattai implementiert.

  • Jetzt liegt es an Ihnen, diese Bibliothek in Ihre App zu integrieren und den Nutzern eine nahtlose, bessere Erfahrung zu bieten.

  • Nachdem wir nun die Echtzeit-Anrufe behandelt haben, möchte ich das Mikrofon an meinen großartigen Kollegen Yacine übergeben, der euch die Funktion „Zum Teilen aneinanderhalten“ vorstellen wird.

  • Vielen Dank.

Herausforderungen bei der Peer-to-Peer-Freigabe

  • Ich weiß, dass Sie alle ein wenig müde sind, aber keine Sorge, das ist der letzte Abschnitt.

  • Ich werde also die Peer-to-Peer-Dateifreigabe behandeln, aber es gibt auch andere Anwendungsfälle für die Peer-to-Peer-Freigabe.

  • Im mobilen Ökosystem werden jeden Tag mehr als 14 Milliarden Fotos und Videos in sozialen Medien und Messaging-Apps geteilt. Das ist eine enorme Menge.

  • Und all diese Dateien werden meist über die Cloud geteilt. Du nimmst also das Bild auf, lädst es über deinen bevorzugten Dienst hoch und die andere Person muss es dann auf der anderen Seite herunterladen.

  • Das Gleiche passiert, wenn Sie jemanden oder einen Freund in ein soziales Netzwerk einladen, um jemanden zur Zusammenarbeit an einer Playlist oder einem Dokument einzuladen. Normalerweise müssen Sie den Benutzernamen oder die E-Mail-Adresse eingeben, was auf Ihrer Tastatur eine Weile dauert.

  • Man kann sich leicht vertun, was nicht gerade ideal ist, vor allem, wenn man direkt nebeneinander sitzt und sich denkt: „Oh, ich muss das über die Cloud senden und warten.“

  • Es ist einfach nicht gut.

  • Heutzutage haben wir alle Smartphones, Tablets und Foldables, mit denen wir tolle Fotos machen können.

  • Wenn du sie aber über bestimmte soziale Medien oder Messaging-Apps teilst, werden sie aus Leistungs- und Speichergründen komprimiert. Das führt dazu, dass sie auf deinem tollen Display ziemlich verpixelt oder einfach nicht in der besten Qualität angezeigt werden.

  • Das ist nicht die beste Erfahrung, die du auf deinem Premium-Gerät machen möchtest.

  • Zweitens sind die Dateien jetzt ziemlich groß.

  • Ein einzelnes Bild ist leicht 10 MB groß, ein 5-minütiges Video etwa 500 MB. Das Hoch- und Herunterladen dauert also eine ganze Weile.

  • Und schließlich müssen Sie auf der anderen Seite auf diese Latenz warten.

  • Manchmal sind Sie vielleicht in einem Bus unterwegs, der voll ist, und während der Bus fährt, haben Sie nicht die beste Latenz, die beste Netzabdeckung und manchmal wird die Dateiübertragung abgebrochen, manchmal wegen dieser wirklich langen Latenz, Sie haben vielleicht ein Timeout und die Dateiübertragung wird abgebrochen, was für einen Nutzer wirklich frustrierend ist.

  • Anstatt sich also auf die Cloud zu verlassen, um all diese Dateien zu teilen, und darauf warten zu müssen, warum nicht ein lokales Netzwerk wie Bluetooth oder WLAN nutzen?

  • Wenn Sie das selbst versuchen, müssen Sie sich mit dem Bluetooth-Manager, dem WLAN-Manager und dem Schreiben eines eigenen Protokolls auseinandersetzen. Das ist nicht jedermanns Sache.

  • Aber eigentlich gibt es bereits etwas auf Android.

  • Quick Share ist bereits auf dem Gerät verfügbar.

Quick Share – Übersicht

  • Mit Quick Share auf Android-Geräten ist diese Abstraktion bereits im System vorhanden. Sie können Dateien einfach mit einem anderen Gerät teilen und müssen sich nicht um die Verwaltung kümmern.

  • Die Funktion ist bereits im Teilen-Menü verfügbar und wechselt zwischen den beiden Geräten, je nachdem, welches Protokoll für Ihr Gerät am besten geeignet ist. Sie ist auf Marshmallow und höher verfügbar, was heutzutage eine ziemlich große Zielgruppe ist, oder?

  • Und schließlich hilft die vertraute Benutzeroberfläche und UX, die Sie auf der Seite sehen, Ihren eigenen Nutzern.

  • Sie kennen die Benutzeroberfläche und müssen nicht lernen, wie sie zu verwenden ist, da sie sie bereits kennen.

  • Aber wir wissen, dass Entwickler auch die Kontrolle über die Nutzererfahrung haben möchten.

  • Sie kennen Ihre App und Ihre Nutzer wahrscheinlich besser als wir.

  • Warum also nicht Quick Share direkt in Ihre App einbinden?

  • Wir haben mit Entwicklern gesprochen und festgestellt, dass Quick Share bereits weit verbreitet ist.

  • Google Play ist auf Android-Geräten, Chromebooks, seit Kurzem auch auf Google Books und auf Windows-Geräten verfügbar. Das sind mehr als 3 Milliarden Geräte – eine riesige Zahl.

  • Mit dem Pixel 11 haben wir auch die Funktion „Zum Teilen aneinanderhalten“ eingeführt.

  • Anstatt das Gerät auszuwählen, mit dem Sie etwas teilen möchten, können Sie einfach tippen. Dank NFC wird das Gerät direkt für Sie gefunden.

  • Sie können also ein Foto oder eine Datei auswählen, zwei Geräte aneinanderhalten und die Inhalte werden direkt geteilt.

  • Und wir wissen, dass es Entwickler gibt, die die Kontrolle über die Nutzererfahrung behalten möchten.

  • Wir wissen, dass dies die beste Möglichkeit für Ihre Nutzer ist, eine bestimmte Funktion zu verwenden, in diesem Fall die Dateifreigabe.

  • Warum also nicht Quick Share in Ihre App einbetten?

  • Deshalb stellen wir euch nächstes Jahr das Quick Share SDK zur Verfügung.

  • Sie können die Funktion direkt in Ihrer App verwenden. Sie ist bereits mit Sicherheitsfunktionen ausgestattet, z. B. mit einer Ende-zu-Ende-Verschlüsselung zwischen beiden Geräten.

  • Da die Übertragung zwischen diesen lokalen Netzwerken erfolgt, haben Sie die volle Geschwindigkeit, natürlich in Ihrem WLAN zu Hause, aber auch zwischen Ihnen beiden kann automatisch ein Hotspot erstellt werden, ohne dass Sie sich darum kümmern müssen.

  • Auf Geräten, die Wi-Fi Direct unterstützen, ist die Funktion bereits enthalten.

  • Und die Dateien, die du mit deiner tollen Kamera aufgenommen hast, behalten ihre beste Auflösung und Detailtreue, sodass du sie auf deinem großen Bildschirm gut sehen kannst.

  • Da es auf lokalen Netzwerken basiert, kannst du es auch in der U-Bahn verwenden, selbst in den tiefsten Bereichen. Du hast immer noch ein Netzwerk, um Inhalte mit deinen Freunden zu teilen.

  • Auch im Flugzeug, ich weiß nicht, wie viele tausend Meter über dem Boden, können Sie diese Dateien teilen.

  • Und schließlich ist es für Sie als Entwickler eine enorme Kostenersparnis in Bezug auf Daten, da Ihre Nutzer erstens nicht ihren Datentarif verwenden müssen, was besonders auf Reisen ein großer Vorteil ist.

  • Außerdem müssen Sie die Server nicht selbst verwalten.

  • Letztes Jahr haben wir Entwicklern und Nutzern mitgeteilt, dass sie mit Quick Share Dateien über AirDrop mit iPhones teilen können.

  • Mit Quick Share bieten wir Entwicklern jetzt dieselbe Möglichkeit.

  • Wir werden ein Quick Share iOS SDK für Entwickler mit iOS-Apps anbieten, damit sie Dateien zwischen beiden Plattformen teilen können, unabhängig vom Gerät.

  • Das ist alles nur eine schicke Präsentation. Sehen wir uns jetzt kurz den Code an.

  • Wir haben versucht, es so einfach wie möglich zu machen.

  • Sie starten eine QuickShareClient und hängen die Dateien an, die Sie teilen möchten. Sobald die Sitzung beginnt, wird das schöne modale Fenster angezeigt, das Sie hier auf der Seite sehen. In der Ansicht am unteren Rand können die Nutzer die Dateien sehen, die als Vorschau gesendet werden. Wenn Sie die Inhalte als vertraulich einstufen, können Sie das auch mit der Option auf dem Bildschirm aktivieren.

    // Final API may undergo further updates
    val quickShareClient = QuickShareClient.create(activity)
    val attachment = FileAttachment(
      fileName = "report.pdf",
      size = 1_048_576L,
      uri = fileUri,
      mimeType = "application/pdf",
      // Sensitive content will be masked in preview
      isSensitiveContent = false,
    )
    
    val content = ShareableContent.withFiles(listOf(attachment))
    val request = SharingRequest.withContent(content)
    val session = quickShareClient.startSharingSession(request, lifecycleOwner)
  • Wenn Sie eine Datei empfangen möchten, ist es dasselbe. Natürlich fügen Sie in diesem Fall keinen Anhang hinzu. Sobald die Sitzung gestartet wird, müssen Sie sich um nichts mehr kümmern.

    // Final API may undergo further updates
    val quickShareClient = QuickShareClient.create(activity)
    val request = SharingRequest.withoutContent()
    val session = quickShareClient.startSharingSession(request, lifecycleOwner)
    
    // Open the Quick Share overlay in receive mode
    session.launchWindow(activity, contentSelectionIntent = null)
  • Bisher haben Sie auf beiden Seiten nur 20 Zeilen Code gesehen.

  • Mit Quick Share und „Zum Teilen aneinanderhalten“ können Dateien durch einfaches Berühren von Geräten geteilt werden. Das ermöglicht einen neuen Anwendungsfall.

  • Ihre App könnte auf dem anderen Gerät geschlossen sein. Wenn Sie darauf tippen, wird Ihre App automatisch geöffnet, sobald die Datei übertragen wurde. Sie erhalten dann den URI, um beispielsweise eine Verarbeitung auf dem Gerät durchzuführen oder dem Nutzer direkt in Ihrer App etwas anzuzeigen.

    // Final API may undergo further updates
    val quickShareClient = QuickShareClient.create(activity)
    val urlAttachment = TextAttachment(
      text = groupAddUrl,
      type = TextAttachment.Type.URL,
    )
    
    val content = ShareableContent.withText(urlAttachment)
    val appLink = AppLink("https://myapp.com/group/invite")
    val request = SharingRequest.withContent(content, appLink)
    val session = quickShareClient.startSharingSession(request, lifecycleOwner)
  • Sie müssen die App also nicht auf dem Empfängergerät öffnen.

  • Mit „Zum Teilen aneinanderhalten“ wird sie automatisch geöffnet.

  • Und wenn Sie möchten, dass Ihre Benutzeroberfläche auf diese Dateiübertragung reagiert, können Sie einfach einen Kotlin-Flow verwenden, um alle Ereignisse zu verarbeiten.

    // Final API may undergo further updates
    scope.launch {
      for (event in session.sharingEvents) {
        when (event) {
          is SharingEvent.SendComplete ->
            println("Send finished")
          is SharingEvent.ReceiveComplete ->
            println("Received, files=${event.content?.files?.size}")
          else -> println("other event")
        }
      }
    }

Zusammenfassung

  • Wir haben heute schon viel gesehen, also komme ich zum Schluss.

  • Und keine Sorge, Sie haben ein paar Minuten Zeit, um eine Pause einzulegen.

  • CompanionDeviceManager ist die beste API für alle Bluetooth-Interaktionen, auch für kontinuierliche Bluetooth-Interaktionen, wie Hanrui bereits gezeigt hat.

  • Wenn Ihre App Voice-over-IP-Anrufe unterstützt, muss dies nicht der Hauptanwendungsfall sein. Es kann beispielsweise sein, dass Ihre App Support-Clients enthält, über die der Nutzer den Support anrufen kann.

  • Auch in diesem Fall ist Jetpack Telecom die beste Lösung für die Verarbeitung von VoIP-Anrufen unter Android.

  • Und schließlich ist Quick Share, wie bereits erwähnt, die Lösung für die Peer-to-Peer-Dateifreigabe oder das Öffnen von Apps mit „Zum Teilen aneinanderhalten“.

  • Vielen Dank an alle. Bevor Sie gehen, noch eine kurze Sache: Wir sind danach draußen, um Ihre Fragen zu beantworten.

  • Wenn Sie Fragen zu den vorgestellten Anwendungsfällen haben, können Sie sich gern an uns wenden.

  • Und vergessen Sie nicht, Ihre Erkenntnisse aus Ihrem Vortrag oder anderen Sitzungen in den sozialen Medien zu teilen.

  • Wir haben den #AndroidDevSummit.

  • Noch einmal: Es ist uns sehr wichtig, dass wir Interaktionen haben und dass die Leute die Veranstaltung wirklich genießen.

  • Es ist schön zu wissen, dass wir eine Community haben, die wir auf jeden Fall wiedersehen möchten.

  • Am Infodesk kannst du dir deinen Social Charm abholen und ihn an deinen Android-Bot-Schlüsselanhänger hängen.

  • Habe ich es richtig gemacht?

  • Vielen Dank.

Code

Code-Snippets

XML-Snippet bei 06:17 in „Übersicht über Companion-Apps“
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                 android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Kotlin-Snippet bei 06:34 in „Übersicht über Companion-Apps“
class BleForegroundService : Service() {
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      …
      return START_STICKY
   }
}
XML-Snippet bei 08:03 in „Implementation using Companion Device Manager“
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
Kotlin-Snippet bei 08:38 in „Implementierung mit Companion Device Manager“
val paringRequest = AssociationRequest.Builder()
   .addDeviceFilter(deviceFilter)
   .setSingleDevice(true)
   .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
   .build()

companionDeviceManager.associate(paringRequest, ContextCompat.getMainExecutor(this),
   object : CompanionDeviceManager.Callback() {
       override fun onAssociationPending(intentSender: IntentSender) {
           intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
       }

       override fun onAssociationCreated(associationInfo: AssociationInfo) { … }

       override fun onFailure(error: CharSequence?) { … }
   }
)
Kotlin-Snippet bei 09:47 in „Implementation using Companion Device Manager“
class BleCompanionDeviceService : CompanionDeviceService() {

   override fun onDevicePresenceEvent(event: DevicePresenceEvent) {
       when (event.event) {
           DevicePresenceEvent.EVENT_BLE_APPEARED -> onDeviceAppeared(event.associationId)
           DevicePresenceEvent.EVENT_BLE_DISAPPEARED -> onDeviceDisappeared(event.associationId)
       }
   }

   private fun onDeviceAppeared(associationId: Int) { … }

   private fun onDeviceDisappeared(associationId: Int) { … }
}
XML-Snippet bei 18:29 in „Introducing Jetpack Telecom“
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Kotlin-Snippet bei 18:36 in „Introducing Jetpack Telecom“
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)

callsManager.registerAppWithTelecom(
    capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                   CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)
Kotlin-Snippet bei 18:55 in „Introducing Jetpack Telecom“
val attributes = CallAttributesCompat(
    displayName = "Kukk Nat",
    address = Uri.parse("tel:+16505550199"),
    direction = CallAttributesCompat.DIRECTION_INCOMING,
    callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL
)

callsManager.addCall(
    attributes, onAnswer, onDisconnect, onSetActive, onSetInactive
) {
    // Coroutine CallControlScope active for the duration of the call
}
Kotlin-Snippet bei 19:49 in „Introducing Jetpack Telecom“
callsManager.addCall(attributes, onAnswer, onDisconnect, onSetActive, onSetInactive) {
    // 1. React to hardware mute toggles from Watch, Car, or Headset
    launch {
        isMuted.collect { muted -> audioEngine.setMicMuted(muted) }
    }

    // 2. Observe available endpoints & request declarative switches
    launch {
        currentCallEndpoint.collect { endpoint -> updateUi(endpoint) }
    }
    requestEndpointChange(preferredBluetoothEndpoint)
}
Kotlin-Snippet bei 29:31 in „Quick Share – Übersicht“
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val attachment = FileAttachment(
  fileName = "report.pdf",
  size = 1_048_576L,
  uri = fileUri,
  mimeType = "application/pdf",
  // Sensitive content will be masked in preview
  isSensitiveContent = false,
)

val content = ShareableContent.withFiles(listOf(attachment))
val request = SharingRequest.withContent(content)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
Kotlin-Snippet bei 29:57 in „Quick Share – Übersicht“
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val request = SharingRequest.withoutContent()
val session = quickShareClient.startSharingSession(request, lifecycleOwner)

// Open the Quick Share overlay in receive mode
session.launchWindow(activity, contentSelectionIntent = null)
Kotlin-Snippet bei 30:23 in „Quick Share – Übersicht“
// Final API may undergo further updates
val quickShareClient = QuickShareClient.create(activity)
val urlAttachment = TextAttachment(
  text = groupAddUrl,
  type = TextAttachment.Type.URL,
)

val content = ShareableContent.withText(urlAttachment)
val appLink = AppLink("https://myapp.com/group/invite")
val request = SharingRequest.withContent(content, appLink)
val session = quickShareClient.startSharingSession(request, lifecycleOwner)
Kotlin-Snippet bei 30:49 in „Quick Share – Übersicht“
// Final API may undergo further updates
scope.launch {
  for (event in session.sharingEvents) {
    when (event) {
      is SharingEvent.SendComplete ->
        println("Send finished")
      is SharingEvent.ReceiveComplete ->
        println("Received, files=${event.content?.files?.size}")
      else -> println("other event")
    }
  }
}