Developer Relations Engineer
Poiché gli utenti passano da smartphone, indossabili e periferiche audio, offrire esperienze cross-device fluide è diventato essenziale per il coinvolgimento. Tuttavia, l'utilizzo di approcci di networking standard spesso comporta un consumo elevato della batteria, picchi di latenza e sessioni utente frammentate.
Questo approfondimento introduce API Android specializzate create per la comunicazione multi-dispositivo, offrendo pattern efficienti in termini di memoria e consumo energetico per mantenere le transizioni tra i dispositivi istantanee, affidabili e intuitive.
Trascrizione
Ciao.
Ti diamo il benvenuto nel corso "Abilitazione di interazioni cross-device fluide".
Sono Hanrui e oggi sono qui con i miei colleghi, Yacine e Nataraj.
Quando parliamo di connettività, spesso ci vengono in mente un collegamento di rete di base o le cuffie Bluetooth.
Su Android, la connettività è molto più ampia.
Si tratta di far funzionare meglio i dispositivi insieme.
Oggi esamineremo gli scenari cross-device più comuni e ti mostreremo l'API più adatta per ciascuno.
Per molto tempo, abbiamo creato app per un solo dispositivo, lo smartphone.
Poi i dispositivi indossabili hanno preso piede.
In breve tempo, è diventato molto comune avere uno smartwatch accoppiato a uno smartphone, sincronizzare i dati in background o rispondere direttamente alle chiamate.
Aggiungi auricolari wireless.
Ora gli utenti possono cambiare dispositivo audio durante una chiamata, passando dallo smartwatch allo smartphone, agli auricolari o all'auto.
Più di recente, molti utenti hanno iniziato a portare con sé anche un secondo dispositivo con schermo grande, come un pieghevole o un tablet.
Si aspettano che l'app funzioni senza problemi su questi dispositivi.
Questo vale anche per laptop e Chromebook, dove gli utenti possono eseguire app per Android e rimanere connessi direttamente dalla loro scrivania.
Già nel 2023, un utente medio possedeva da tre a quattro dispositivi.
Gli utenti si aspettano molto di più di una semplice funzionalità autonoma su questi dispositivi.
Cercano un'esperienza straordinaria di integrazione tra i dispositivi.
Questa esperienza di collaborazione è particolarmente evidente quando le persone si trovano nella stessa stanza, come noi oggi, e vogliono un modo semplice per condividere foto, link o app con le persone vicine.
I numeri mostrano che l'ecosistema è enorme e in rapida crescita.
Quest'anno, nel mondo ci sono circa 22 miliardi di dispositivi IoT, con una crescita del 14% su base annua.
Tra tutti questi endpoint, il Wi-Fi rappresenta un terzo e il Bluetooth un quarto.
La connettività cross-device non è più un caso limite.
Fa parte della nostra vita quotidiana.
Tuttavia, oggi la maggior parte delle app continua a creare le proprie funzionalità cross-device su API di rete e Bluetooth di basso livello, il che crea tre sfide.
Innanzitutto, batteria e memoria.
Mantenere attivi i servizi e le scansioni in background consuma batteria e memoria.
In secondo luogo, la latenza.
I ritardi imprevedibili interrompono le chiamate e la sincronizzazione dei dati in tempo reale.
Infine, l'attrito della connessione.
La gestione del rilevamento, dell'accoppiamento e della riconnessione su tutti i dispositivi è complessa e porta facilmente a un'esperienza utente interrotta.
Per eliminare questi problemi, abbiamo creato API apposite in tre aree principali per semplificare l'utilizzo delle funzionalità cross-device.
Inizierò con le app complementari, poi Nataraj parlerà delle app VoIP e infine Yacine parlerà della condivisione P2P.
Diamo prima un'occhiata alle app complementari.
Un'app complementare è un'app che installi sullo smartphone per consentire a un dispositivo complementare, come uno smartwatch o un sensore di glucosio, di funzionare correttamente con lo smartphone.
In questa sezione ci concentreremo sui dispositivi connessi tramite Bluetooth.
In genere esistono quattro categorie di dispositivi complementari.
Innanzitutto, i dispositivi medici, come i sensori di glucosio e i monitor dei parametri vitali.
E dispositivi indossabili, come smartwatch e fitness band.
Cuffie con accoppiamento rapido e controlli smart.
E kit per auto, per funzionalità come le chiavi digitali dell'auto e le chiamate in vivavoce.
Ogni categoria ha i propri user journey chiave, ma tutti dipendono da una connessione affidabile tra il dispositivo e lo smartphone.
Per i dispositivi medici, questa connessione può essere addirittura fondamentale per la vita.
Esaminiamo quattro esigenze comuni che riscontriamo in queste categorie.
Innanzitutto, la gestione dei dispositivi.
L'app deve trovare il dispositivo giusto tra tutti quelli nelle vicinanze, ad esempio lo smartwatch qui, non gli auricolari o lo speaker, e poi avviare il flusso di accoppiamento per connettersi.
In secondo luogo, il controllo della presenza.
L'app deve sapere quando il dispositivo è nelle vicinanze e quando non lo è.
Quando il dispositivo viene visualizzato, l'app deve essere attivata per poter svolgere il suo lavoro anche se l'utente non l'ha aperta.
Terzo, manutenzione della connessione.
Una volta connesse, le app richiedono che la connessione sia affidabile e persistente.
Se la connessione si interrompe, ad esempio se l'utente esce dal raggio d'azione e poi rientra, l'app deve riconnettersi automaticamente, anche se non è in esecuzione in quel momento.
Quarto, sincronizzazione e trasferimento dei dati.
È qui che si trova il vero valore.
Sincronizzazione delle letture dei sensori, push degli aggiornamenti firmware o invio delle impostazioni al dispositivo.
Per i dispositivi medici, questo può essere fondamentale.
Una lettura mancata può significare un avviso mancato.
Tutte queste operazioni richiedono che il processo dell'app sia attivo e connesso al momento giusto.
In che modo le app hanno soddisfatto tutte queste esigenze?
In genere, le app si basano molto sul servizio in primo piano.
Ecco un'implementazione tipica.
Se hai già creato un'app complementare, questo potrebbe sembrarti familiare.
Dichiari le autorizzazioni di scansione e connessione Bluetooth, le autorizzazioni per i servizi in primo piano e il tipo di servizio in primo piano del dispositivo connesso, quindi l'autorizzazione alle notifiche perché un servizio in primo piano deve mostrare una notifica persistente.
<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" />Poi, esegui un servizio sticky per mantenere attivo il processo dell'app.
class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}Questo approccio funziona, ma consuma batteria e memoria perché il servizio è sempre in esecuzione, anche quando il dispositivo non è nelle vicinanze.
Ecco perché abbiamo creato Gestione dispositivi associati, o CDM in breve.
Innanzitutto, può attivare l'app in base alla presenza del dispositivo e, finché il dispositivo rimane nel raggio d'azione, il sistema manterrà attivo il processo dell'app.
Nessun servizio in primo piano necessario.
In secondo luogo, CDM supporta i profili dei dispositivi.
Con un profilo del dispositivo, l'utente concede tutte le autorizzazioni necessarie alla tua app con un solo tocco, anziché tramite una serie di finestre di dialogo di autorizzazione.
Ciò migliora notevolmente l'esperienza utente e il tasso di conversione.
Il profilo per smartwatch è disponibile a partire da Android 12, mentre quello per occhiali a partire da Android 14.
In Android 17, abbiamo aggiunto due nuovi profili specifici per dispositivi medici e fitness tracker.
Oggi esistono anche alcuni profili con privilegi solo per le app di sistema.
Se l'ecosistema dovesse esprimere un'esigenza più ampia, potremmo prendere in considerazione l'idea di renderle disponibili a un maggior numero di sviluppatori.
Utilizzare CDM è davvero facile.
Prendiamo come esempio un dispositivo medico.
Innanzitutto, dichiara le autorizzazioni necessarie: una per il dispositivo medico, una per l'esecuzione in background e una per osservare la presenza del dispositivo.
<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" />Una cosa da sottolineare: a differenza delle autorizzazioni che abbiamo appena visto con l'implementazione del servizio in primo piano, tutte le autorizzazioni elencate qui sono autorizzazioni normali, il che significa che verranno concesse al momento dell'installazione, senza finestra di dialogo di richiesta.
L'unica cosa che l'utente deve approvare è l'associazione.
Vediamo come creare questa associazione.
Qui creiamo una richiesta di associazione con un filtro per i dispositivi in modo che venga visualizzato solo il dispositivo corretto.
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 su true perché vogliamo una sola corrispondenza e imposta il profilo su medical.
Poi chiama l'addetto.
CDM gestisce la scansione.
In onAssociationPending, riceveremo un IntentSender.
Avviala. Il sistema mostra una finestra di dialogo di associazione.
Con un profilo medico, questo tocco non solo approva l'associazione stessa, ma concede anche tutte le autorizzazioni necessarie al profilo, ignorando persino la modalità Non disturbare, che è fondamentale per gli avvisi critici.
Una volta che l'utente tocca Consenti, riceveremo un onAssociationCreated con AssociationInfo, che include un ID associazione.
Poi possiamo osservare la presenza del dispositivo chiamando startObservingDevicePresence con l'ID associazione.
Devi anche implementare un servizio che estenda 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) { … }
}Quando il dispositivo entra nel raggio d'azione, il sistema si associa al tuo servizio anche se la tua app non è in esecuzione.
Il sistema chiamerà onDevicePresenceEvent con EVENT_BLE_APPEARED.
Dopodiché, potrai connetterti e sincronizzare i dati.
Quando il dispositivo esce, riceverai EVENT_BLE_DISAPPEARED.
Dopodiché, puoi eseguire la pulizia.
Nessun servizio sempre attivo e nessuna notifica solo per mantenere l'app attiva.
Dobbiamo ammettere che CDM presenta alcune limitazioni che gli sviluppatori devono tenere presenti.
Innanzitutto, le versioni del sistema operativo.
La maggior parte delle funzionalità di CDM è stata introdotta in Android 12 e 13.
Tuttavia, in Android 16 sono stati introdotti alcuni miglioramenti interessanti e in Android 17 i nuovi profili.
Pertanto, controlla la versione del sistema operativo in fase di runtime e mantieni un comportamento di riserva per le versioni precedenti.
Detto questo, l'SDK target aumenta ogni anno e l'SDK minimo tende a seguire.
Per far fronte alla pressione sulla memoria che le app stanno affrontando ora e che affronteranno in futuro, consigliamo vivamente di eseguire la migrazione delle app dal servizio in primo piano a CDM il prima possibile.
Se hai un caso d'uso che non può essere supportato da CDM oggi, faccelo sapere.
In secondo luogo, attualmente CDM supporta la scansione in background, ma non l'annuncio in background.
Raramente ci viene segnalata questa esigenza.
Tuttavia, se la tua app richiede che lo smartphone agisca come periferica e trasmetta continuamente in background, saremo lieti di saperne di più.
Terzo, CDM utilizza la modalità di scansione a basso consumo.
Sacrifica un po' di reattività in tempo reale per risparmiare batteria e memoria.
Quindi, non aspettarti risvegli istantanei.
Infine, non è possibile esaminare il payload.
Non puoi registrarti per una caratteristica o un dato pubblicitario specifico.
Tuttavia, una volta che il dispositivo è nel raggio d'azione, l'app si riattiva.
In questo modo, può connettersi e controllare il payload in base alle esigenze.
Questo è tutto ciò che c'è da sapere sulle app complementari e sul CDM.
Ora lascio la parola a Nataraj per le app VoIP.
CompanionDeviceManager è lo strumento giusto se la tua app possiede una periferica dedicata e comunica continuamente con questa.
Ciao,
Sono Nataraj K R.
Ora parlerò del prossimo scenario cross-device, ovvero le chiamate in tempo reale.
Oggi il modo in cui le persone gestiscono le chiamate sta cambiando.
Oltre il 40% delle chiamate viene effettuato su reti VoIP.
E Android sta giocando un ruolo importante in questo.
Infatti, il 71% di queste chiamate avviene su dispositivi Android, ovvero circa una chiamata su due.
Non si tratta solo delle chiamate.
Anche l'ecosistema di dispositivi di proprietà delle persone sta crescendo in modo massiccio.
Ci sono oltre 250 milioni di auto con Android Auto in circolazione e stiamo anche spedendo più di un miliardo di dispositivi Bluetooth agli utenti.
Auricolari, smartwatch Wear OS, Android, auto con Android Auto, speaker Bluetooth sono tutti i dispositivi che gli utenti possiedono oltre allo smartphone e vogliono che le loro chiamate vengano trasferite su tutti questi dispositivi senza interruzioni.
Ad esempio, l'utente riceve una chiamata e dà un'occhiata allo smartwatch per conoscere le informazioni sul chiamante.
Risponderà alla chiamata sullo smartwatch.
Disattivavano o riattivavano l'audio in base alle esigenze, inserivano gli auricolari e continuavano la chiamata.
Se vogliono uscire, salgono in auto e si aspettano che la chiamata passi con cambio fluido all'abitacolo dell'auto.
Questa è diventata una routine normale per l'utente e non è un'esperienza premium.
Per un utente, questo è un requisito di base e spetta a noi sviluppatori Android fornire questa esperienza fluida nelle nostre app VoIP agli utenti.
Tuttavia, se provi a creare tutto questo da zero, ti ritroverai con tre problemi principali.
Uno, la frammentazione con i socket Bluetooth.
Poiché il sistema non sa che la tua app sta gestendo una chiamata, dovresti creare socket Bluetooth personalizzati e sessioni multimediali personalizzate per consentire la proiezione della chiamata sullo smartwatch o su Android Auto.
Il secondo problema è il focus audio.
Poiché devi gestire le API AudioManager e Bluetooth di basso livello, si verificano perdite audio e non è una priorità per il sistema, giusto?
La tua app sta effettuando una chiamata VoIP, quindi ogni volta che arriva una chiamata dell'operatore, questa viene interrotta e il modem acquisisce l'audio, lasciando la tua app senza audio.
Peggio ancora, anche quando la chiamata cellulare termina, lo stato attivo dell'audio non viene restituito alla tua app.
Il terzo problema è la sincronizzazione.
L'utente toccava l'icona di disattivazione dell'audio sullo smartwatch, ma l'UI dello smartphone mostrava ancora la chiamata con l'audio attivo.
Questo crea molta confusione.
L'utente disconnetteva la chiamata dal piantone dello sterzo dell'auto, ma lo smartphone mostrava ancora un avviso di chiamata in corso.
Quando succede, l'utente si sente molto frustrato.
Per risolvere esattamente tutti questi problemi, abbiamo creato Jetpack Telecom.
Quindi, registri la chiamata una volta con Jetpack Telecom utilizzando CallsManager e Jetpack Telecom funge da coordinatore centrale delle chiamate di sistema.
Si occupa di proiettare la chiamata sullo smartwatch, su Android Auto e su altre piattaforme remote di proprietà dell'utente.
Inoltre, si occupa della gestione della priorità audio.
E, soprattutto, tutto questo funziona perfettamente, fino ad Android 8.
Proiettare la chiamata sullo schermo dello smartwatch o dell'auto è solo metà dell'opera, perché l'utente vuole ovviamente controllare lo stato della chiamata da questi dispositivi.
Vorrebbero disattivare l'audio della chiamata dallo smartwatch o riattivarlo in base alle esigenze.
Vorrebbero riagganciare la chiamata dal piantone dello sterzo dell'auto o toccando due volte gli auricolari.
Pertanto, Jetpack Telecom ascolta questi eventi hardware e li presenta come callback in un unico posto, ovvero CallControlScope.
Tutto ciò che devi fare è ascoltare questi callback, gestire il motore di chiamata e aggiornare lo stato della chiamata di conseguenza.
Jetpack Telecom si occupa di sincronizzare lo stato della chiamata tra i dispositivi.
Quindi, dietro le quinte, la libreria offre vantaggi in due categorie.
In primo luogo, ovviamente, risolve tutti i problemi di cui abbiamo parlato.
Quindi, invece di dover gestire le API AudioManager di basso livello, la libreria fornisce endpoint di chiamata dichiarativi, gestisce l'audio focus e si sincronizza tra i dispositivi.
Si occupa di trasferire la chiamata tra i dispositivi.
Inoltre, si occupa anche delle chiamate in arrivo sulla rete mobile.
Ma l'altro vantaggio della libreria è che, poiché è basata su Kotlin, ogni volta che aggiungi una chiamata con Jetpack Telecom, viene aperto un ambito di coroutine Kotlin.
In questo modo, sfrutta le coroutine Kotlin, che rimangono attive esattamente per la durata della chiamata.
Pertanto, qualsiasi job eseguito in relazione alla chiamata viene eliminato automaticamente non appena la chiamata viene disconnessa.
In questo modo, non avrai job non autorizzati e il ciclo di vita delle chiamate verrà gestito dalla libreria.
Ora ti starai chiedendo: questa libreria è fantastica e voglio implementarla, giusto?
È semplice come contare fino a 3.
Il passaggio 1 consiste, ovviamente, nel dichiarare la dipendenza.
Dopodiché, dichiara l'autorizzazione richiesta, ovvero MANAGE_OWN_CALLS, quindi crea un'istanza di un oggetto CallsManager.
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />Dopo aver creato un'istanza dell'oggetto CallsManager, registra la tua app con le funzionalità che vuoi fornire con l'app, ad esempio videochiamate o streaming delle chiamate.
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)Dopodiché, vai al passaggio 2.
Ora che hai registrato la tua app con Jetpack Telecom, è il momento di gestire le chiamate in entrata e in uscita.
Per farlo, devi creare un oggetto CallAttributesCompat, che contiene i metadati della chiamata, come il displayName del chiamante o del chiamato, l'indirizzo, che è l'endpoint del chiamante, la direzione, che specifica se la chiamata è una chiamata in arrivo o una chiamata in uscita in modo che Jetpack Telecom possa presentare la rispettiva UI su queste piattaforme remote, e infine callType, se si tratta di una chiamata audio o di una videochiamata in modo da ottenere le risorse richieste.
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
}Una volta pronto l'oggetto dei metadati, aggiungilo utilizzando il metodo callsManager.addCall.
In questo modo si apre un ambito di coroutine di controllo delle chiamate.
Dopo aver aggiunto la chiamata, passiamo al passaggio 3.
Il passaggio 3 consiste nell'ascoltare le azioni che l'utente eseguirà da queste piattaforme remote.
Quindi, poiché hai CallControlScope, non devi fare altro che raccogliere i flussi 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)
}Raccogli isMuted per reagire ogni volta che l'utente disattiva l'audio della chiamata da queste piattaforme remote e, di conseguenza, puoi aggiornare lo stato della chiamata.
Raccogli currentCallEndpoint per sapere su quale endpoint sta avvenendo la chiamata, in modo da poter aggiornare la UI di conseguenza.
Puoi mostrare quel particolare dispositivo nella UI dello smartphone.
Ogni volta che l'utente vuole passare da un dispositivo a un altro, basta chiamare il metodo requestEndpointChange con l'endpoint preferito dall'utente.
È davvero semplice.
Con questi tre passaggi, la tua app è pronta e ha integrato Jetpack Telecom.
Ma non è tutto.
Con Jetpack Telecom 1.1.0, introduciamo molte altre funzionalità, come le cronologie unificate delle chiamate, il che significa che tutte le tue chiamate VoIP verranno ora visualizzate in modo nativo nel tastierino del sistema.
Il vantaggio è che gli utenti potranno creare un'istanza di richiamata con un solo tocco direttamente dal tastierino del sistema nell'app.
In questo modo, non dovrai cercare l'app specifica tra tutte le app.
Possono semplicemente creare un'istanza di richiamata dal tastierino del sistema che sono abituati a utilizzare.
Tuttavia, offriamo anche controlli granulari.
Se non vuoi che una chiamata, una chiamata specifica, venga registrata nel sistema, devi solo impostare il flag isLogExcluded su true all'interno dell'oggetto CallAttributesCompat.
L'oggetto dei metadati di cui ho parlato prima, basta impostarlo su true e la chiamata non verrà registrata nel sistema e non verrà visualizzata nel dialer di sistema.
La seconda fantastica funzionalità che verrà introdotta con la versione 1.1.0 è il network slicing 5G.
Quindi, sulle reti degli operatori supportati, le reti ti forniscono sezioni di rete premium, il che significa che per un determinato caso d'uso, l'operatore ti fornirà una sezione di rete con bassa latenza e senza altre interruzioni, in modo da avere una qualità e un'esperienza migliori.
Pertanto, sulle reti degli operatori supportati, questa funzionalità sarà disponibile immediatamente, senza alcun intervento da parte tua, nemmeno una singola riga di codice.
Una volta integrata la libreria, le tue chiamate sfrutteranno queste sezioni di rete e otterrai una latenza e prestazioni migliori.
Tuttavia, se per qualche motivo non vuoi questa funzionalità, puoi disattivarla utilizzando il flag CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.
Jetpack Telecom è già stato implementato dalle principali app di comunicazione globali come Google Meet, WhatsApp, KakaoTalk e Zoho Arattai.
Quindi, ora tocca a te integrare questa libreria nella tua app e offrire un'esperienza migliore e più fluida all'utente.
Ora che abbiamo parlato delle chiamate in tempo reale, vorrei passare la parola al mio fantastico collega Yacine per parlare della condivisione con un tocco.
Grazie.
Ok, so che siete un po' stanchi, ma non preoccupatevi, questa è l'ultima sezione.
Quindi, parlerò della condivisione di file peer-to-peer, ma esistono altri casi d'uso che possiamo realizzare con la condivisione peer-to-peer.
Nell'ecosistema mobile, abbiamo visto che ogni giorno vengono condivisi più di 14 miliardi di foto e video sui social media e sulle app di messaggistica, una quantità enorme.
E tutti questi file vengono condivisi il più delle volte tramite il cloud, quindi scatti la foto, la carichi tramite il tuo servizio preferito e poi l'altra persona deve scaricarla allo stesso modo dall'altra parte.
E la stessa cosa accade quando provi a invitare qualcuno o un amico su un social network per collaborare a una playlist o a un documento: di solito devi digitare il suo nome utente o il suo indirizzo email, il che richiede un po' di tempo sulla tastiera.
Potresti commettere un errore e non è un'esperienza piacevole, soprattutto quando siete seduti uno accanto all'altro e pensi: "Oh, devo inviare tramite il cloud, devo aspettare".
Non è il massimo.
Oggi tutti abbiamo smartphone, dispositivi, tablet e pieghevoli di ultima generazione con cui scattiamo foto incredibili.
Ma quando li condividi su alcuni di questi social media o su alcune di queste app di messaggistica, ovviamente per problemi di prestazioni e spazio di archiviazione, vengono compressi, il che alla fine, sul tuo fantastico schermo, li rende piuttosto pixelati o semplicemente non della migliore qualità.
Non è l'esperienza migliore che vorresti avere sul tuo dispositivo premium.
In secondo luogo, i file ora sono piuttosto grandi.
Una singola immagine può facilmente raggiungere i 10 MB, un video di 5 minuti è di circa 500 MB, quindi il caricamento e il download di tutto questo richiedono un po' di tempo.
Infine, dall'altro lato, devi attendere la latenza.
Ad esempio, se sei su un autobus affollato e in movimento, la latenza e la copertura di rete non sono ottimali e il trasferimento dei file potrebbe essere interrotto o, a causa della latenza molto elevata, potrebbe verificarsi un timeout e il trasferimento dei file potrebbe essere annullato, il che è davvero frustrante per un utente.
Quindi, invece di affidarsi al cloud per condividere tutti questi file e dover aspettare, perché non utilizzare una rete locale, come il Bluetooth o il Wi-Fi?
Ma se provi a farlo autonomamente, ovviamente, devi occuparti del gestore Bluetooth, del gestore Wi-Fi, devi scrivere il tuo protocollo, insomma, non tutti vogliono dedicare del tempo a questo.
In realtà, su Android esiste già qualcosa.
È già disponibile e forse ne hai sentito parlare: Quick Share.
Quick Share su Android ti consente di avere questa astrazione già attiva nel sistema e puoi semplicemente condividere file con un altro dispositivo senza dover gestire tutto questo.
È già disponibile nel foglio di condivisione e passa da un protocollo all'altro per trovare quello migliore per il tuo dispositivo tra i due dispositivi. È disponibile su Marshmallow e versioni successive, che, al giorno d'oggi, è un pubblico piuttosto vasto, giusto?
Infine, la UI e la UX familiari che vedi sul lato aiutano davvero i tuoi utenti.
Quindi, sanno cos'è la UI, non devono imparare a usarla, l'hanno già vista in passato.
Ma sappiamo che gli sviluppatori vogliono anche avere il controllo dell'esperienza.
Conosci la tua app e probabilmente conosci i tuoi utenti meglio di noi.
Perché non integrare Quick Share direttamente nella tua app?
Abbiamo quindi iniziato a parlare con gli sviluppatori e abbiamo notato che Quick Share è già piuttosto diffuso.
È disponibile su Android, su Chromebook, di recente su Google Libri e anche su Windows, ovvero su oltre 3 miliardi di dispositivi, un numero enorme.
Inoltre, con Pixel 11 abbiamo introdotto la possibilità di toccare per condividere.
Quindi, invece di dover scegliere il dispositivo con cui vuoi condividere, puoi semplicemente toccare e, con l'NFC, eseguiamo la rilevabilità direttamente per te.
Quindi, puoi selezionare una foto, ma anche un file, toccare due dispositivi e condividerli direttamente con un tocco.
Inoltre, sappiamo che gli sviluppatori vogliono avere il controllo dell'esperienza.
Sappiamo che è il modo migliore per consentire ai tuoi utenti di utilizzare una funzionalità specifica, in questo caso la condivisione di file.
Quindi, perché non integrare Quick Share nella tua app?
Ecco perché l'anno prossimo metteremo a tua disposizione l'SDK Quick Share.
Potrai utilizzarlo direttamente nella tua app e la sicurezza è già integrata, con la crittografia end-to-end tra i due dispositivi.
Poiché i trasferimenti avvengono tra queste reti locali, avrai la massima velocità, ovviamente nel Wi-Fi di casa, ma anche tra voi due, con la creazione automatica di un hotspot, senza che tu debba occupartene.
Per i dispositivi che supportano Wi-Fi Direct, è già incluso.
Inoltre, i file acquisiti con la tua fantastica fotocamera manterranno la risoluzione e i dettagli migliori, così potrai vederli chiaramente sul tuo grande schermo.
E dato che si basa su reti locali, puoi usarlo in metropolitana, anche nella parte più profonda, e avrai comunque la rete per poter condividere con il tuo amico.
Anche in aereo, non so a quante migliaia di piedi di altezza, nel cielo, potrai comunque condividere i file.
Infine, per te come sviluppatore, è un enorme risparmio in termini di dati, perché innanzitutto i tuoi utenti non devono utilizzare il loro piano dati, il che è un grande vantaggio quando viaggiano come turisti.
Inoltre, non devi occuparti della manutenzione dei tuoi server.
L'anno scorso abbiamo comunicato a sviluppatori e utenti che con Quick Share avrebbero potuto condividere file con i dispositivi iPhone tramite AirDrop.
Con Quick Share, offriamo la stessa cosa agli sviluppatori.
Metteremo a disposizione un SDK Quick Share per iOS per gli sviluppatori che hanno app per iOS, in modo che possano condividere file tra entrambe le piattaforme, indipendentemente dal dispositivo.
Tutto questo è una presentazione elegante, ora diamo un'occhiata rapida al codice.
Abbiamo cercato di renderlo il più semplice possibile.
Ovviamente, avvii una QuickShareClient, devi allegare i file che vuoi condividere e, una volta avviata la sessione, vedrai questa bellissima finestra modale che vedi sul lato, il riquadro inferiore in cui gli utenti potranno vedere i file che vengono inviati come anteprime e, se ritieni che i contenuti siano sensibili, puoi anche attivare questa opzione con l'opzione visualizzata sullo schermo.
// 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)Quando vuoi ricevere un file, sempre all'interno della tua app, la stessa cosa, ovviamente, in questo caso, non aggiungi un allegato e, non appena la sessione viene avviata, non devi occuparti di nulla.
// 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)Finora hai visto letteralmente solo 20 righe di codice su entrambi i lati.
Sappiamo che, con Quick Share e tocca per condividere, la possibilità di condividere file semplicemente toccando i dispositivi consente anche un nuovo caso d'uso.
La tua app potrebbe essere chiusa sull'altro dispositivo e, toccando, non appena il file viene trasferito, la tua app verrà aperta automaticamente e riceverai l'URI per poter eseguire, ad esempio, un'elaborazione su dispositivo o mostrare qualcosa all'utente direttamente all'interno della tua app.
// 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)Quindi, ancora una volta, non è necessario aprire l'app sul dispositivo di destinazione.
Con tocca per condividere, si aprirà automaticamente.
E, naturalmente, se vuoi che la tua UI reagisca al trasferimento di file, basta avere un flusso Kotlin e avrai tutti gli eventi per poterlo elaborare.
// 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")
}
}
}Oggi abbiamo visto molte cose, quindi concludo.
Non preoccuparti, avrai qualche minuto per fare una pausa.
CompanionDeviceManager è l'API migliore per gestire qualsiasi interazione Bluetooth, anche continua, come ha spiegato Hanrui in precedenza.
Se la tua app include chiamate VoIP, non è necessario che questo sia il suo caso d'uso principale. Ad esempio, l'app potrebbe includere un'assistenza clienti che l'utente può chiamare.
Anche in questo caso, il modo migliore per gestire le chiamate VoIP su Android è Jetpack Telecom.
Infine, per qualsiasi app di condivisione o apertura di file peer-to-peer, come ho già detto, con tocca per condividere, Quick Share è la soluzione.
Quindi, grazie a tutti e, prima di andare, solo una cosa veloce: dopo saremo fuori per rispondere alle vostre domande.
Se hai domande su tutti i casi d'uso che abbiamo presentato, puoi contattarci durante l'orario lavorativo.
E non dimenticare di condividere sui social media ciò che hai imparato durante il tuo intervento o altre sessioni.
Abbiamo l'#AndroidDevSummit.
Ancora una volta, è molto importante per noi sapere che abbiamo coinvolgimento, che le persone si stanno davvero divertendo all'evento.
Ci aiuta a capire che abbiamo una community che vogliamo rivedere.
E ritira il tuo charm social da aggiungere al portachiavi del tuo bot Android presso l'infodesk.
Ho fatto bene?
Grazie.
<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" />class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}<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" />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?) { … }
}
)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) { … }
}<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)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
}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)
}// 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)// 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)// 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)// 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")
}
}
}