Podstawy aplikacji

Umożliwianie płynnych interakcji między urządzeniami

Głośniki

Informacje

Informacje o wystąpieniu

Użytkownicy korzystają z telefonów, urządzeń do noszenia i akcesoriów audio, dlatego zapewnienie płynnego działania na różnych urządzeniach stało się kluczowe dla zaangażowania. Jednak poleganie na standardowych metodach sieciowych często prowadzi do rozładowania baterii, skoków opóźnienia i fragmentacji sesji użytkowników.

W tym szczegółowym omówieniu przedstawiamy specjalistyczne interfejsy API Androida stworzone do komunikacji na wiele urządzeń. Oferują one wzorce oszczędzające pamięć i energię, dzięki którym przełączanie się między urządzeniami jest natychmiastowe, niezawodne i intuicyjne.

Podsumowanie

  • Menedżer urządzeń towarzyszących zastępuje usługi na pierwszym planie w przypadku urządzeń peryferyjnych Bluetooth, wybudzając aplikacje na podstawie obecności i zarządzając połączeniami bez rozładowywania baterii. Android 17 dodaje nowe profile przeznaczone specjalnie dla urządzeń medycznych i trackerów aktywności.
  • Jetpack Telecom działa jako koordynator połączeń systemowych w aplikacjach VoIP na połączonych urządzeniach, takich jak zegarki i samochody, i jest zgodny z Androidem 8. Synchronizuje też działania wyciszania i rozłączania za pomocą CallControlScope.
  • Nadchodzące funkcje Jetpack Telecom 1.1.0 obejmują ujednoliconą historię połączeń w dialerze systemowym (SDK 36.1+) i automatyczne dzielenie sieci 5G na obsługiwanych sieciach operatorów.
  • W przyszłym roku wprowadzimy pakiet SDK do szybkiego udostępniania, dzięki któremu deweloperzy będą mogli umieszczać funkcję lokalnego, w pełni szyfrowanego udostępniania plików bezpośrednio w aplikacjach na Androida i iOS. Udostępnianie zbliżeniowe (wprowadzone na Pixelu 11) może nawet automatycznie wybudzić zamkniętą aplikację odbierającą.

Zasoby

Gemini Notebook

Tematy

  • na innym urządzeniu
  • companion-device-manager
  • jetpack-telecom
  • quick-share
  • Bluetooth
  • voip
  • peer-to-peer

Transkrypcja

Pobierz transkrypcję

Wprowadzenie do interakcji na różnych urządzeniach

  • Dzień dobry,

  • Witamy w module „Umożliwianie płynnych interakcji na różnych urządzeniach”.

  • Nazywam się Hanrui, a dzisiaj są ze mną moi koledzy, Yacine i Nataraj.

  • Gdy mówimy o łączności, często myślimy o podstawowym połączeniu sieciowym lub zestawach słuchawkowych Bluetooth.

  • Na Androidzie łączność jest znacznie szerszym pojęciem.

  • Chodzi o to, aby urządzenia lepiej ze sobą współpracowały.

  • Dzisiaj omówimy typowe scenariusze dotyczące różnych urządzeń i pokażemy, który interfejs API jest odpowiedni w każdym z nich.

  • Przez długi czas tworzyliśmy aplikacje na jedno urządzenie – telefon.

  • Potem pojawiły się urządzenia do noszenia.

  • Wkrótce bardzo popularne stało się parowanie zegarka z telefonem, synchronizowanie danych w tle i odbieranie połączeń bezpośrednio na zegarku.

  • Dodaj słuchawki douszne – dodaj bezprzewodowe słuchawki douszne.

  • Teraz użytkownicy mogą przełączać urządzenie audio w trakcie rozmowy – z zegarka na telefon, słuchawki douszne lub samochód.

  • Ostatnio wielu użytkowników nosi ze sobą również drugie urządzenie z dużym ekranem, np. składany telefon lub tablet.

  • Oczekują, że aplikacja będzie działać płynnie na wszystkich tych urządzeniach.

  • Dotyczy to również laptopów i Chromebooków, na których użytkownicy mogą uruchamiać aplikacje na Androida i pozostawać w kontakcie bezpośrednio przy biurku.

  • Nawet w 2023 r. przeciętny użytkownik miał od 3 do 4 urządzeń.

  • Użytkownicy oczekują od tych urządzeń czegoś więcej niż tylko samodzielnych funkcji.

  • Oczekują, że urządzenia będą ze sobą świetnie współpracować.

  • To, że urządzenia działają lepiej razem, jest szczególnie widoczne, gdy ludzie znajdują się w tym samym pomieszczeniu, tak jak my dzisiaj, i chcą w łatwy sposób udostępniać zdjęcia, linki lub aplikacje osobom obok nich.

  • Liczby pokazują, że ekosystem jest ogromny i szybko się rozwija.

  • W tym roku na całym świecie jest około 22 miliardów urządzeń IoT, a ich liczba rośnie o 14% rok do roku.

  • Wśród wszystkich tych punktów końcowych Wi-Fi stanowi 1/3, a Bluetooth – 1/4.

  • Połączenia między urządzeniami nie są już rzadkością.

  • Jest częścią naszego codziennego życia.

  • Jednak obecnie większość aplikacji nadal tworzy funkcje do obsługi wielu urządzeń na podstawie interfejsów API sieci i Bluetootha niskiego poziomu, co wiąże się z 3 wyzwaniami.

  • Po pierwsze: bateria i pamięć.

  • Działanie skanowania i usług w tle zużywa baterię i pamięć.

  • Po drugie, czas oczekiwania.

  • Nieprzewidywalne opóźnienia zakłócają połączenia i synchronizację danych w czasie rzeczywistym.

  • Po trzecie, problemy z połączeniem.

  • Zarządzanie wykrywaniem, parowaniem i ponownym łączeniem na wszystkich urządzeniach jest skomplikowane i może łatwo prowadzić do problemów z obsługą.

  • Aby rozwiązać te problemy, stworzyliśmy specjalne interfejsy API w 3 głównych obszarach, które ułatwiają korzystanie z funkcji na różnych urządzeniach.

  • Zacznę od aplikacji towarzyszących, a potem Nataraj omówi aplikacje VoIP, a Yacine opowie o udostępnianiu P2P.

Omówienie aplikacji towarzyszących

  • Najpierw przyjrzyjmy się aplikacjom towarzyszącym.

  • Aplikacja towarzysząca to aplikacja, którą instalujesz na telefonie, aby urządzenie towarzyszące, takie jak zegarek lub czujnik glukozy, mogło z nim współpracować.

  • W tej sekcji skupimy się na urządzeniach połączonych przez Bluetooth.

  • Zwykle wyróżnia się 4 kategorie urządzeń towarzyszących.

  • Po pierwsze, urządzenia medyczne, takie jak czujniki glukozy i monitory parametrów życiowych.

  • oraz urządzeń do noszenia, takich jak zegarki i opaski fitnessowe.

  • Zestawy słuchawkowe z szybkim parowaniem i inteligentnym sterowaniem.

  • oraz zestawów samochodowych, np. do cyfrowych kluczyków samochodowych i rozmów w trybie głośnomówiącym.

  • Każda kategoria ma własne kluczowe ścieżki użytkownika, ale wszystkie zależą od niezawodnego połączenia między urządzeniem a telefonem.

  • W przypadku urządzeń medycznych takie połączenie może mieć nawet kluczowe znaczenie dla życia.

  • Przyjrzyjmy się 4 typowym potrzebom, które występują w tych kategoriach.

  • Po pierwsze, zarządzanie urządzeniami.

  • Aplikacja musi znaleźć odpowiednie urządzenie wśród wszystkich urządzeń w pobliżu, np. zegarek, a nie słuchawki czy głośnik, a następnie rozpocząć proces parowania, aby nawiązać połączenie.

  • Po drugie, sprawdzanie obecności.

  • Aplikacja musi wiedzieć, kiedy urządzenie jest w pobliżu, a kiedy nie.

  • Gdy urządzenie się pojawi, aplikacja musi zostać wybudzona, aby mogła wykonać swoje zadanie, nawet jeśli użytkownik jej nie otworzył.

  • Po trzecie, utrzymanie połączenia.

  • Po połączeniu aplikacje wymagają niezawodnego i trwałego połączenia.

  • Jeśli połączenie zostanie przerwane, np. gdy użytkownik wyjdzie poza zasięg, a potem wróci, aplikacja musi automatycznie połączyć się ponownie, nawet jeśli nie jest wtedy uruchomiona.

  • Po czwarte, synchronizacja i przenoszenie danych.

  • To jest prawdziwa wartość.

  • synchronizowanie odczytów czujników, przesyłanie aktualizacji oprogramowania układowego lub wysyłanie ustawień na urządzenie;

  • W przypadku urządzeń medycznych może to mieć kluczowe znaczenie.

  • Pominięty odczyt może oznaczać pominięty alert.

  • Wszystkie te funkcje wymagają, aby proces aplikacji był aktywny i połączony w odpowiednim czasie.

  • Jak aplikacje spełniają te potrzeby?

  • Tradycyjnie aplikacje w dużym stopniu polegały na usłudze na pierwszym planie.

  • Oto typowa implementacja.

  • Jeśli masz już doświadczenie w tworzeniu aplikacji towarzyszących, może Ci się to wydawać znajome.

  • Deklarujesz uprawnienia do skanowania Bluetooth i łączenia, uprawnienia usługi na pierwszym planie i typ usługi na pierwszym planie połączonego urządzenia, a następnie zgodę na wyświetlanie powiadomień, ponieważ usługa na pierwszym planie musi wyświetlać stałe powiadomienie.

    <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" />
  • Następnie uruchom usługę sticky, aby utrzymać proces aplikacji.

    class BleForegroundService : Service() {
       override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          …
          return START_STICKY
       }
    }
  • To działa, ale zużywa baterię i pamięć, ponieważ usługa działa cały czas, nawet gdy urządzenie nie znajduje się w pobliżu.

  • Dlatego stworzyliśmy Menedżera urządzeń towarzyszących (w skrócie CDM).

  • Po pierwsze, może wybudzić aplikację na podstawie obecności urządzenia i dopóki urządzenie pozostaje w zasięgu, system będzie utrzymywać proces aplikacji aktywny.

  • Nie jest potrzebna usługa na pierwszym planie.

  • Po drugie, CDM obsługuje profile urządzeń.

  • Dzięki profilowi urządzenia użytkownik może przyznać wszystkie uprawnienia, których potrzebuje aplikacja, jednym kliknięciem, zamiast przechodzić przez serię okien dialogowych.

  • Znacznie poprawia to wrażenia użytkowników i zwiększa współczynnik konwersji.

  • Profil zegarka jest dostępny od Androida 12, a okularów – od Androida 14.

  • W Androidzie 17 dodaliśmy 2 nowe profile przeznaczone specjalnie dla urządzeń medycznych i trackerów aktywności.

  • Obecnie istnieją też pewne profile uprzywilejowane, które są przeznaczone tylko dla aplikacji systemowych.

  • Jeśli zauważymy, że w ekosystemie jest większe zapotrzebowanie na te funkcje, możemy rozważyć udostępnienie ich większej liczbie deweloperów.

Implementacja za pomocą Menedżera urządzeń towarzyszących

  • Korzystanie z CDM jest bardzo proste.

  • Jako przykładu użyjmy urządzenia medycznego.

  • Najpierw zadeklaruj niezbędne uprawnienia: jedno dla urządzenia medycznego, jedno do działania w tle i jedno do obserwowania obecności urządzenia.

    <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" />
  • Warto zauważyć, że w przeciwieństwie do uprawnień, które właśnie widzieliśmy w przypadku implementacji usługi na pierwszym planie, wszystkie wymienione tutaj uprawnienia są normalnymi uprawnieniami, co oznacza, że zostaną przyznane podczas instalacji bez wyświetlania okna z prośbą o potwierdzenie.

  • Użytkownik musi tylko zatwierdzić powiązanie.

  • Zobaczmy, jak utworzyć to powiązanie.

  • Tutaj tworzymy żądanie powiązania z filtrem urządzeń, aby wyświetlało się tylko właściwe urządzenie.

    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 na wartość true, ponieważ chcemy tylko jednego dopasowania, i ustaw profil na medical.

  • Następnie zadzwoń do współpracownika.

  • Skanowanie jest obsługiwane przez CDM.

  • W onAssociationPending otrzymamy IntentSender.

  • Uruchom ją, a system wyświetli okno powiązania.

  • W przypadku profilu medycznego jedno kliknięcie nie tylko zatwierdza samo powiązanie, ale też przyznaje wszystkie uprawnienia, których potrzebuje profil, a nawet pomija tryb Nie przeszkadzać, co jest kluczowe w przypadku alertów o znaczeniu krytycznym.

  • Gdy użytkownik kliknie Zezwól, otrzymamy onAssociationCreated z AssociationInfo, który zawiera identyfikator powiązania.

  • Następnie możemy obserwować obecność urządzenia, wywołując funkcję startObservingDevicePresence z tym identyfikatorem powiązania.

  • Musisz też wdrożyć usługę, która rozszerza CompanionDeviceService.

    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) { … }
    }
  • Gdy urządzenie znajdzie się w zasięgu, system połączy się z Twoją usługą, nawet jeśli aplikacja nie jest uruchomiona.

  • System zadzwoni do: onDevicePresenceEvent, używając numeru: EVENT_BLE_APPEARED.

  • Następnie możesz połączyć i zsynchronizować urządzenia.

  • Gdy urządzenie opuści obszar, otrzymasz EVENT_BLE_DISAPPEARED.

  • Następnie możesz wykonać czyszczenie.

  • Nie ma usługi działającej zawsze ani powiadomień, które mają tylko utrzymać działanie aplikacji.

  • Musimy przyznać, że CDM ma kilka ograniczeń, o których deweloperzy muszą pamiętać.

  • Po pierwsze, wersje systemu operacyjnego.

  • Większość funkcji CDM pojawiła się w Androidzie 12 i 13.

  • W Androidzie 16 pojawiły się jednak pewne ciekawe ulepszenia, a w Androidzie 17 – nowe profile.

  • Dlatego sprawdzaj wersję systemu operacyjnego w czasie działania i zachowaj kreację zastępczą dla starszych wersji.

  • Warto jednak pamiętać, że docelowy pakiet SDK jest co roku aktualizowany, a minimalny pakiet SDK zwykle podąża za nim.

  • Aby rozwiązać problem z obciążeniem pamięci, z którym aplikacje borykają się obecnie i będą borykać się w przyszłości, zdecydowanie zalecamy jak najszybsze przejście z usługi działającej na pierwszym planie na CDM.

  • Jeśli masz przypadek użycia, który nie jest obecnie obsługiwany przez CDM, daj nam znać.

  • Po drugie, obecnie CDM obsługuje skanowanie w tle, ale nie rozgłaszanie w tle.

  • Rzadko słyszymy o takiej potrzebie.

  • Jeśli jednak Twoja aplikacja wymaga, aby telefon działał jako urządzenie peryferyjne i stale rozgłaszał informacje w tle, chętnie dowiemy się więcej na ten temat.

  • Po trzecie, CDM używa trybu skanowania o niskim poborze energii.

  • Zmniejsza nieco czas reakcji w czasie rzeczywistym, aby oszczędzać baterię i pamięć.

  • Nie oczekuj więc natychmiastowego wybudzenia.

  • Na koniec: nie można podglądać ładunku.

  • Nie możesz zarejestrować się w celu uzyskania określonej charakterystyki lub danych reklamowych.

  • Gdy jednak urządzenie znajdzie się w zasięgu, aplikacja zostanie wybudzona.

  • Dzięki temu może się połączyć i sprawdzić ładunek w razie potrzeby.

  • To wszystko na temat aplikacji towarzyszących i CDM.

  • Teraz Nataraj opowie o aplikacjach VoIP.

Problemy z połączeniami VoIP między urządzeniami

  • CompanionDeviceManager to odpowiednie narzędzie, jeśli Twoja aplikacja ma dedykowane urządzenie peryferyjne i stale się z nim komunikuje.

  • Cześć!

  • Nazywam się Nataraj K R.

  • Teraz omówię kolejny scenariusz dotyczący wielu urządzeń, czyli połączenia w czasie rzeczywistym.

  • Obecnie sposób, w jaki ludzie obsługują połączenia, się zmienia.

  • Ponad 40% połączeń jest realizowanych przez sieci VoIP.

  • Android odgrywa w tym procesie ważną rolę.

  • 71% tych połączeń jest wykonywanych na urządzeniach z Androidem, co oznacza, że co drugie połączenie jest wykonywane na takim urządzeniu.

  • Nie chodzi tylko o połączenia.

  • Ekosystem urządzeń, które posiadają użytkownicy, również znacznie się rozrasta.

  • Po drogach jeździ ponad 250 milionów samochodów z Androidem Auto, a użytkownicy mają do dyspozycji ponad miliard urządzeń Bluetooth.

  • Słuchawki douszne, zegarki z Wear OS, samochody z Androidem Auto, głośniki Bluetooth – to urządzenia, które użytkownicy mają wokół telefonu i chcą, aby połączenia były płynnie przekazywane między nimi.

  • Na przykład użytkownik otrzyma połączenie i spojrzy na zegarek, aby sprawdzić informacje o rozmówcy.

  • Odbierze połączenie na zegarku.

  • W razie potrzeby wyciszali lub wyłączali wyciszenie, wkładali słuchawki douszne i kontynuowali rozmowę.

  • Jeśli chcą wyjść, wsiadają do samochodu i oczekują, że rozmowa zostanie płynnie przełączona na kabinę samochodu.

  • Dla użytkownika stało się to normalną rutyną, która nie zapewnia mu najlepszych wrażeń.

  • Dla użytkownika jest to podstawowe wymaganie, a zadaniem deweloperów aplikacji na Androida jest zapewnienie użytkownikom płynnego działania aplikacji VoIP.

  • Jeśli jednak spróbujesz zbudować to wszystko od podstaw, napotkasz 3 poważne problemy.

  • Po pierwsze, fragmentacja gniazd Bluetooth.

  • System nie wie, że aplikacja obsługuje połączenie, więc musisz utworzyć niestandardowe gniazda Bluetooth i niestandardowe sesje multimedialne, aby połączenie mogło być wyświetlane na zegarku lub w Androidzie Auto.

  • Drugi problem to skupienie dźwięku.

  • Ponieważ musisz korzystać z niskopoziomowych interfejsów AudioManager API i Bluetooth API, dochodzi do wycieków dźwięku, a nie jest to priorytet dla systemu, prawda?

  • Aplikacja prowadzi połączenie VoIP, więc gdy pojawia się połączenie od operatora, przerywa je, a modem przejmuje dźwięk, przez co aplikacja nie ma dźwięku.

  • Co gorsza, po zakończeniu połączenia komórkowego fokus audio nie zostanie zwrócony do aplikacji.

  • Trzeci problem to synchronizacja.

  • Użytkownik klikał wyciszenie na zegarku, ale interfejs telefonu nadal pokazywał, że połączenie nie jest wyciszone.

  • To powoduje duże zamieszanie.

  • Użytkownik rozłączał połączenie z kolumny kierownicy samochodu, ale na telefonie nadal wyświetlało się powiadomienie o trwającym połączeniu.

  • To bardzo frustrujące dla użytkownika, gdy coś takiego się dzieje.

Przedstawiamy Jetpack Telecom

  • Aby rozwiązać wszystkie te problemy, stworzyliśmy Jetpack Telecom.

  • Dzięki temu rejestrujesz połączenie raz w Jetpack Telecom za pomocą CallsManager, a Jetpack Telecom działa jako centralny koordynator połączeń systemowych.

  • Odpowiada za wyświetlanie połączenia na zegarku, w Androidzie Auto i na innych urządzeniach zdalnych użytkownika.

  • Zajmuje się też obsługą fokusu audio.

  • I co najważniejsze, wszystko to działa bezproblemowo, nawet na urządzeniach z Androidem 8.

  • Wyświetlanie połączenia na ekranie zegarka lub samochodu to tylko połowa sukcesu, ponieważ użytkownik chce oczywiście kontrolować stan połączenia z tych urządzeń.

  • Chcą wyciszyć połączenie na zegarku lub wyłączyć wyciszenie, gdy jest to potrzebne.

  • Chce zakończyć połączenie za pomocą przycisku na kolumnie kierownicy lub przez dwukrotne dotknięcie słuchawek dousznych.

  • Jetpack Telecom nasłuchuje tych zdarzeń sprzętowych i prezentuje je jako wywołania zwrotne w jednym miejscu, czyli w CallControlScope.

  • Wystarczy, że będziesz nasłuchiwać tych wywołań zwrotnych, obsługiwać silnik połączeń i odpowiednio aktualizować stan połączenia.

  • Jetpack Telecom odpowiada za synchronizowanie stanu połączenia między urządzeniami.

  • Biblioteka zapewnia korzyści w 2 obszarach.

  • Po pierwsze, oczywiście, rozwiązuje wszystkie problemy, o których mówiliśmy.

  • Zamiast więc korzystać z niskopoziomowych interfejsów API AudioManager, biblioteka udostępnia deklaratywne punkty końcowe połączeń, zarządza aktywnością audio i synchronizuje urządzenia.

  • Odpowiada za przekazywanie połączenia między urządzeniami.

  • Zajmuje się też połączeniami komórkowymi.

  • Inną zaletą biblioteki jest to, że jest ona oparta na języku Kotlin. Za każdym razem, gdy dodajesz połączenie za pomocą Jetpack Telecom, otwiera się zakres współprogramu Kotlin.

  • Dzięki temu wykorzystuje współprogramy Kotlin, które działają dokładnie przez czas trwania połączenia.

  • Dlatego każde zadanie uruchomione w związku z połączeniem jest automatycznie usuwane, gdy tylko połączenie zostanie rozłączone.

  • Dzięki temu nie pozostaną żadne niepożądane zadania, a biblioteka zajmie się też cyklem życia wywołania.

  • Pewnie zastanawiasz się, jak wdrożyć tę świetną bibliotekę.

  • To bardzo proste.

  • Krok 1. Oczywiście musisz zadeklarować zależność.

  • Następnie zadeklaruj wymagane uprawnienie, czyli MANAGE_OWN_CALLS, i utwórz instancję obiektu CallsManager.

    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
  • Po utworzeniu instancji obiektu CallsManager zarejestruj aplikację z funkcjami, które chcesz udostępniać, takimi jak rozmowy wideo czy strumieniowanie połączeń.

    // Initialize CallsManager & register supported capabilities once
    val callsManager = CallsManager(context)
    
    callsManager.registerAppWithTelecom(
        capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                       CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
    )
  • Następnie krok 2.

  • Po zarejestrowaniu aplikacji w Jetpack Telecom nadszedł czas na obsługę połączeń przychodzących i wychodzących.

  • W tym celu musisz utworzyć obiekt CallAttributesCompat, który zawiera metadane połączenia, takie jak displayName dzwoniącego lub odbiorcy, adres, czyli punkt końcowy dzwoniącego, kierunek, który określa, czy połączenie jest połączeniem przychodzącym czy połączeniem wychodzącym, dzięki czemu Jetpack Telecom może wyświetlać odpowiedni interfejs na tych zdalnych platformach, oraz callType, czyli informację, czy jest to rozmowa głosowa czy rozmowa wideo, aby uzyskać wymagane zasoby.

    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
    }
  • Gdy obiekt metadanych będzie gotowy, dodaj go za pomocą metody callsManager.addCall.

  • Spowoduje to otwarcie zakresu współprogramu sterowania połączeniami.

  • Po dodaniu połączenia przejdź do kroku 3.

  • Krok 3 polega na nasłuchiwaniu działań, które użytkownik wykona na tych platformach zdalnych.

  • Ponieważ masz CallControlScope, wystarczy, że zbierzesz przepływy Kotlin.

    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)
    }
  • Zbierasz isMuted, aby reagować, gdy użytkownik wyciszy połączenie na tych urządzeniach zdalnych, i odpowiednio aktualizować stan połączenia.

  • Zbierz currentCallEndpoint, aby dowiedzieć się, w którym punkcie końcowym odbywa się obecnie połączenie, i odpowiednio zaktualizować interfejs.

  • Możesz wyświetlić to konkretne urządzenie w interfejsie telefonu.

  • Jeśli użytkownik chce przełączyć się na inne urządzenie, wystarczy wywołać metodę requestEndpointChange z preferowanym punktem końcowym.

  • To takie proste.

  • Po wykonaniu tych 3 czynności aplikacja jest gotowa do działania i ma zintegrowany pakiet Jetpack Telecom.

  • Ale to nie wszystko.

  • W Jetpack Telecom 1.1.0 wprowadzamy znacznie więcej funkcji, takich jak ujednolicona historia połączeń, co oznacza, że wszystkie połączenia VoIP będą teraz wyświetlane natywnie w dialerze systemowym.

  • Zaletą tego rozwiązania jest to, że użytkownicy będą mogli zainicjować oddzwonienie jednym kliknięciem bezpośrednio z dialera systemowego w aplikacji.

  • Nie musisz więc szukać konkretnej aplikacji wśród wszystkich aplikacji.

  • Mogą po prostu oddzwonić, korzystając z dialera systemowego, do którego są przyzwyczajeni.

  • ale zapewniamy też szczegółowe ustawienia.

  • Jeśli nie chcesz, aby połączenie, konkretne połączenie, było rejestrowane w systemie, musisz tylko ustawić flagę isLogExcluded jako „true” w obiekcie CallAttributesCompat.

  • Wystarczy, że ustawisz wartość obiektu metadanych, o którym mówiłem wcześniej, na „true”, a połączenie nie zostanie zarejestrowane w systemie i nie pojawi się w dialerze systemowym.

  • Druga świetna funkcja, która pojawi się w wersji 1.1.0, to segmentacja sieci 5G.

  • W obsługiwanych sieciach operatorów otrzymujesz dostęp do wycinków sieci premium. Oznacza to, że w konkretnym przypadku operator udostępnia wycinek sieci o niskim opóźnieniu i bez zakłóceń, co zapewnia lepszą jakość i komfort użytkowania.

  • W obsługiwanych sieciach operatorów ta funkcja będzie dostępna od razu, więc nie musisz nic robić ani pisać żadnego kodu.

  • Po zintegrowaniu biblioteki połączenia będą korzystać z tych wycinków sieci, co zapewni mniejsze opóźnienia i lepszą wydajność.

  • Jeśli jednak z jakiegoś powodu nie chcesz korzystać z tej funkcji, możesz z niej zrezygnować, używając flagi CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.

  • Jetpack Telecom został już wdrożony w najpopularniejszych aplikacjach do komunikacji na świecie, takich jak Google Meet, WhatsApp, KakaoTalk i Zoho Arattai.

  • Teraz to od Ciebie zależy, czy zintegrujesz tę bibliotekę ze swoją aplikacją i zapewnisz użytkownikom lepsze wrażenia.

  • Teraz, gdy omówiliśmy już połączenia w czasie rzeczywistym, chciałbym przekazać głos mojemu koledze Yacine, który opowie o funkcji udostępniania zbliżeniowego.

  • Dziękujemy.

Wyzwania związane z udostępnianiem plików w modelu peer-to-peer

  • Wiem, że jesteście już trochę zmęczeni, ale nie martwcie się, to ostatnia sekcja.

  • Omówię udostępnianie plików w modelu peer-to-peer, ale są też inne przypadki użycia, w których można wykorzystać udostępnianie w tym modelu.

  • W ekosystemie mobilnym każdego dnia udostępnianych jest ponad 14 miliardów zdjęć i filmów w mediach społecznościowych i aplikacjach do obsługi wiadomości. To ogromna liczba.

  • Większość tych plików jest udostępniana przez chmurę. Robisz zdjęcie, przesyłasz je za pomocą ulubionej usługi, a odbiorca musi je pobrać w ten sam sposób.

  • To samo dzieje się, gdy próbujesz zaprosić kogoś lub znajomego do sieci społecznościowej, do współpracy nad playlistą lub dokumentem. Zwykle musisz wpisać nazwę użytkownika lub adres e-mail, co zajmuje trochę czasu na klawiaturze.

  • Możesz popełnić błąd, co nie jest zbyt przyjemne, zwłaszcza gdy siedzicie obok siebie i musisz wysłać coś przez chmurę i czekać.

  • Nie jest to najlepsze rozwiązanie.

  • Obecnie wszyscy mamy nowoczesne telefony, urządzenia, tablety i składane telefony, którymi robimy niesamowite zdjęcia.

  • Jednak gdy udostępniasz je w niektórych mediach społecznościowych lub aplikacjach do obsługi wiadomości, ze względu na problemy z wydajnością i pamięcią są one kompresowane, co sprawia, że na Twoim niesamowitym ekranie są dość rozpikselowane lub po prostu nie są najlepszej jakości.

  • To nie jest najlepsze doświadczenie, jakiego można oczekiwać od urządzenia premium.

  • Po drugie, pliki są teraz dość duże.

  • Pojedyncze zdjęcie może mieć 10 MB, a 5-minutowy film – 500 MB, więc przesyłanie i pobieranie takich plików zajmuje sporo czasu.

  • Z drugiej strony musisz poczekać na opóźnienie.

  • Czasami wsiadasz do zatłoczonego autobusu i gdy autobus jedzie, nie masz najlepszego opóźnienia ani najlepszego zasięgu sieci. Czasami przesyłanie plików zostaje przerwane, a czasami z powodu bardzo dużego opóźnienia może dojść do przekroczenia limitu czasu i przesyłanie plików zostaje anulowane, co jest bardzo frustrujące dla użytkownika.

  • Zamiast polegać na chmurze i czekać na udostępnienie wszystkich plików, dlaczego nie skorzystać z sieci lokalnej, takiej jak Bluetooth lub Wi-Fi?

  • Jeśli jednak spróbujesz zrobić to samodzielnie, musisz oczywiście poradzić sobie z menedżerem Bluetooth, menedżerem Wi-Fi i napisać własny protokół, a nie każdy chce poświęcać na to czas.

  • Ale na Androidzie jest już coś takiego.

  • Jest już dostępna i być może już o niej słyszeliście – to szybkie udostępnianie.

Omówienie szybkiego udostępniania

  • Szybkie udostępnianie na Androidzie pozwala na korzystanie z tej abstrakcji w systemie. Możesz po prostu udostępniać pliki innemu urządzeniu i nie musisz zarządzać tym procesem.

  • Jest już dostępna w arkuszu udostępniania i przełącza się między protokołami, aby wybrać ten, który jest najlepszy dla Twojego urządzenia. Działa na urządzeniach z systemem Marshmallow i nowszym, co oznacza, że obecnie ma całkiem dużą grupę odbiorców, prawda?

  • I wreszcie, znajomy interfejs i UX, który widzisz z boku, naprawdę pomaga użytkownikom.

  • Wiedzą, jak wygląda interfejs, i nie muszą uczyć się, jak go używać, bo już go znają.

  • Wiemy jednak, że deweloperzy chcą też mieć kontrolę nad tym, jak użytkownicy korzystają z ich aplikacji.

  • Znasz swoją aplikację i użytkowników prawdopodobnie lepiej niż my.

  • Dlaczego więc nie mieć szybkiego udostępniania bezpośrednio w aplikacji?

  • Zaczęliśmy więc rozmawiać z deweloperami i zauważyliśmy, że szybkie udostępnianie jest już dość powszechne.

  • Jest dostępna na Androidzie, Chromebookach, od niedawna w Książkach Google, a także w systemie Windows. To ponad 3 miliardy urządzeń – ogromna liczba.

  • W Pixelu 11 wprowadziliśmy też możliwość udostępniania zbliżeniowego.

  • Zamiast wybierać urządzenie, któremu chcesz udostępnić treści, możesz po prostu kliknąć, a dzięki NFC wykryjemy je bezpośrednio za Ciebie.

  • Możesz wybrać zdjęcie, ale też plik. Wystarczy, że zetkniesz ze sobą 2 urządzenia, a plik zostanie udostępniony.

  • Wiemy, że deweloperzy chcą mieć kontrolę nad tym, jak użytkownicy korzystają z aplikacji.

  • Wiemy, że to najlepszy sposób, aby użytkownicy mogli korzystać z określonej funkcji, w tym przypadku udostępniania plików.

  • Dlaczego więc nie umieścić szybkiego udostępniania w aplikacji?

  • Dlatego w przyszłym roku udostępnimy pakiet SDK szybkiego udostępniania bezpośrednio dla Ciebie.

  • Możesz używać go bezpośrednio w aplikacji, a jego bezpieczeństwo jest zapewnione dzięki pełnemu szyfrowaniu między urządzeniami.

  • Przesyłanie odbywa się między tymi sieciami lokalnymi, więc masz pełną prędkość, oczywiście w domowej sieci Wi-Fi, ale też między Wami może zostać automatycznie utworzony hotspot, więc nie musisz się tym zajmować.

  • W przypadku urządzeń obsługujących Wi-Fi Direct jest ona już dostępna.

  • Pliki, które zostały zrobione za pomocą niesamowitego aparatu, zachowają najlepszą rozdzielczość i szczegóły, dzięki czemu będzie można je ładnie wyświetlać na dużym ekranie.

  • A ponieważ opiera się na sieciach lokalnych, możesz z niej korzystać w metrze, nawet w najgłębszych miejscach. Nadal będziesz mieć sieć, aby udostępniać treści znajomym.

  • Nawet w samolocie, na wysokości kilku tysięcy metrów, nadal możesz udostępniać pliki.

  • I wreszcie, dla Ciebie jako dewelopera, to ogromna oszczędność kosztów związanych z danymi, ponieważ po pierwsze, Twoi użytkownicy nie muszą korzystać z pakietu danych, co jest bardzo ważne, gdy podróżują jako turyści.

  • Nie musisz też utrzymywać tego na własnych serwerach.

  • W zeszłym roku poinformowaliśmy deweloperów i użytkowników, że dzięki szybkiemu udostępnianiu będą mogli udostępniać pliki na iPhone'ach za pomocą AirDrop.

  • Dzięki szybkiemu udostępnianiu deweloperzy mogą korzystać z tej samej funkcji.

  • Udostępnimy pakiet SDK szybkiego udostępniania na iOS deweloperom, którzy mają aplikacje na iOS, aby umożliwić udostępnianie plików między obiema platformami niezależnie od urządzenia.

  • To wszystko to tylko ładna prezentacja. Przyjrzyjmy się teraz szybko kodowi.

  • Staraliśmy się, aby było to jak najprostsze.

  • Oczywiście inicjujesz QuickShareClient, musisz załączyć pliki, które chcesz udostępnić, a gdy rozpoczniesz sesję, zobaczysz to piękne okno modalne, które widzisz z boku, czyli planszę dolną, na której użytkownicy będą mogli zobaczyć podgląd wysyłanych plików. Jeśli uważasz, że treść jest poufna, możesz też włączyć tę opcję, którą widzisz na ekranie.

    // 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)
  • Jeśli chcesz otrzymać plik, również robisz to w aplikacji. W tym przypadku nie dodajesz załącznika. Po rozpoczęciu sesji nie musisz się już niczym przejmować.

    // 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)
  • Do tej pory widzieliście dosłownie 20 linii kodu po obu stronach.

  • Wiemy, że dzięki szybkiemu udostępnianiu i dotknięciu, aby udostępnić, możliwość udostępniania plików przez dotknięcie urządzeń umożliwia też nowe zastosowanie.

  • Aplikacja może być zamknięta na drugim urządzeniu, ale po kliknięciu, gdy tylko plik zostanie przesłany, aplikacja zostanie automatycznie otwarta i otrzymasz identyfikator URI, który umożliwi np. przetwarzanie na urządzeniu lub wyświetlenie czegoś użytkownikowi bezpośrednio w aplikacji.

    // 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)
  • Nie musisz więc otwierać aplikacji na urządzeniu, które odbiera dane.

  • Dzięki funkcji udostępniania zbliżeniowego otworzy się ona automatycznie.

  • Jeśli chcesz, aby interfejs reagował na transfer plików, możesz użyć Kotlin Flow, który zawiera wszystkie zdarzenia potrzebne do przetworzenia tego procesu.

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

Podsumowanie i zakończenie

  • Dzisiaj omówiliśmy już wiele kwestii, więc będę kończyć.

  • Bez obaw, będziesz mieć kilka minut na przerwę.

  • CompanionDeviceManager to najlepszy interfejs API do obsługi wszelkich interakcji Bluetooth, w tym ciągłych, jak wcześniej wspomniał Hanrui.

  • Jeśli Twoja aplikacja obsługuje połączenia VoIP, nie musi to być jej główny przypadek użycia. Może to być na przykład sekcja pomocy, w której użytkownik może zadzwonić do zespołu pomocy.

  • W tym przypadku najlepszym sposobem na obsługę połączeń VoIP na Androidzie jest Jetpack Telecom.

  • I na koniec – w przypadku udostępniania plików w sieci P2P lub otwierania aplikacji, jak już wspomnieliśmy, rozwiązaniem jest szybkie udostępnianie.

  • Dziękuję wszystkim. Zanim wyjdziecie, jeszcze jedna rzecz: po zakończeniu będziemy na zewnątrz, aby odpowiedzieć na Wasze pytania.

  • Jeśli masz pytania dotyczące wszystkich przedstawionych przez nas przypadków użycia, możesz skontaktować się z nami w godzinach pracy.

  • Nie zapomnij też podzielić się w mediach społecznościowych tym, czego udało Ci się dowiedzieć podczas swojej prezentacji lub innych sesji.

  • Mamy #AndroidDevSummit.

  • Bardzo ważne jest dla nas, aby wiedzieć, że wydarzenie cieszy się zainteresowaniem i że uczestnicy są zadowoleni.

  • Dzięki temu wiemy, że mamy społeczność, którą chcemy zobaczyć ponownie.

  • Odbierz też w punkcie informacyjnym zawieszkę, którą możesz dodać do breloka z botem Androida.

  • Czy wszystko jest w porządku?

  • Dziękujemy.

Kod

Fragmenty kodu

Fragment kodu XML w 06:17 w filmie „Overview of 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" />
Fragment kodu w języku Kotlin w 06:34 w filmie „Omówienie aplikacji towarzyszących”
class BleForegroundService : Service() {
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      …
      return START_STICKY
   }
}
Fragment kodu XML w 08:03 w filmie „Implementacja przy użyciu Menedżera urządzeń towarzyszących”
<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" />
Fragment kodu w języku Kotlin w 08:38 w filmie „Implementacja przy użyciu Menedżera urządzeń towarzyszących”
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?) { … }
   }
)
Fragment kodu w języku Kotlin w 09:47 w filmie „Implementacja przy użyciu Menedżera urządzeń towarzyszących”
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) { … }
}
Fragment kodu XML w 18:29 w filmie „Introducing Jetpack Telecom”
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Fragment kodu w języku Kotlin w 18:36 w filmie „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
)
Fragment kodu w języku Kotlin w 18:55 w filmie „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
}
Fragment kodu w Kotlinie w 19:49 w filmie „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)
}
Fragment kodu w Kotlinie w 29:31 w filmie „Omówienie szybkiego udostępniania”
// 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)
Fragment kodu w języku Kotlin w 29:57 w filmie „Omówienie szybkiego udostępniania”
// 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)
Fragment kodu w Kotlinie w 30:23 w filmie „Omówienie szybkiego udostępniania”
// 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)
Fragment kodu w języku Kotlin w 30:49 w filmie „Omówienie szybkiego udostępniania”
// 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")
    }
  }
}