Ingénieur Relations avec les développeurs
Les utilisateurs passant d'un téléphone à un accessoire connecté ou à un périphérique audio, il est devenu essentiel de leur offrir des expériences fluides sur tous leurs appareils pour les fidéliser. Cependant, s'appuyer sur des approches de mise en réseau standards entraîne souvent une décharge de la batterie, des pics de latence et des sessions utilisateur fragmentées.
Cette présentation détaillée présente des API Android spécialisées conçues pour la communication multi-appareil, offrant des modèles économes en mémoire et en énergie pour que les transitions entre les appareils soient instantanées, fiables et intuitives.
Transcription
Bonjour,
Bienvenue dans le module "Permettre des interactions fluides entre les appareils".
Je m'appelle Hanrui et je suis accompagné aujourd'hui de mes collègues, Yacine et Nataraj.
Quand on parle de connectivité, on pense souvent à une liaison réseau de base ou à des casques Bluetooth.
Sur Android, la connectivité est bien plus vaste que cela.
L'objectif est de faire en sorte que les appareils fonctionnent mieux ensemble.
Aujourd'hui, nous allons examiner des scénarios multi-appareils courants et vous indiquer l'API adaptée à chacun d'eux.
Pendant longtemps, nous avons créé des applications pour un seul appareil, le téléphone.
Puis les wearables ont décollé.
Il est rapidement devenu très courant d'associer une montre connectée à un téléphone, de synchroniser les données en arrière-plan ou de prendre des appels directement.
Ajouter des écouteurs sans fil.
Désormais, les utilisateurs peuvent changer d'appareil audio en cours d'appel, en passant de leur montre à leur téléphone, à leurs écouteurs ou à leur voiture.
Plus récemment, de nombreux utilisateurs se sont également équipés d'un deuxième appareil à grand écran, comme un appareil pliable ou une tablette.
Ils s'attendent à ce que l'application fonctionne correctement sur ces appareils.
Cela s'étend aux ordinateurs portables et aux Chromebooks, où les utilisateurs peuvent exécuter des applications Android et rester connectés directement depuis leur bureau.
En 2023, un utilisateur moyen possédait déjà entre trois et quatre appareils.
Les utilisateurs attendent plus que des fonctionnalités autonomes sur ces appareils.
Ils recherchent une expérience Better Together extraordinaire.
Cette expérience "Better Together" est particulièrement évidente lorsque des personnes se trouvent dans la même pièce, comme nous aujourd'hui, et souhaitent partager facilement des photos, des liens ou des applications avec les personnes à côté d'elles.
Les chiffres montrent que l'écosystème est immense et qu'il se développe rapidement.
Cette année, on compte environ 22 milliards d'appareils IoT dans le monde, soit une augmentation de 14 % d'une année sur l'autre.
Parmi tous ces points de terminaison, le Wi-Fi représente un tiers et le Bluetooth un quart.
La connectivité inter-appareils n'est plus un cas particulier.
Elle fait partie de notre quotidien.
Pourtant, aujourd'hui, la plupart des applications continuent de créer leurs fonctionnalités multi-appareils sur des API réseau et Bluetooth de bas niveau, ce qui pose trois problèmes.
Tout d'abord, la batterie et la mémoire.
L'exécution de services et d'analyses en arrière-plan décharge la batterie et consomme de la mémoire.
Deuxièmement, la latence.
Des retards imprévisibles perturbent les appels et la synchronisation des données en temps réel.
Troisièmement, les frictions de connexion.
La gestion de la découverte, de l'association et de la reconnexion sur tous les appareils est complexe et peut facilement nuire à l'expérience utilisateur.
Pour vous éviter ces difficultés, nous avons conçu des API dédiées dans trois domaines majeurs afin de simplifier les fonctionnalités multi-appareils.
Je vais commencer par les applications associées, puis Nataraj parlera des applications VoIP et Yacine du partage P2P.
Commençons par les applications associées.
Une application associée est une application que vous installez sur votre téléphone pour qu'un appareil associé, comme une montre connectée ou un capteur de glucose, puisse fonctionner correctement avec lui.
Dans cette section, nous allons nous concentrer sur les appareils connectés en Bluetooth.
Il existe généralement quatre catégories d'appareils associés.
Tout d'abord, les dispositifs médicaux, comme les capteurs de glucose et les moniteurs de signes vitaux.
et les accessoires connectés, comme les montres connectées et les bracelets d'activité.
Casques, avec Association express et commandes intelligentes.
et les kits voiture, pour des fonctionnalités telles que les clés de voiture numériques et les appels mains libres.
Chaque catégorie possède ses propres parcours utilisateur clés, mais tous dépendent d'une connexion fiable entre l'appareil et le téléphone.
Pour les dispositifs médicaux, cette connexion peut même être vitale.
Examinons quatre besoins courants que nous constatons dans ces catégories.
Tout d'abord, la gestion des appareils.
L'application doit trouver le bon appareil parmi tous ceux qui se trouvent à proximité (ici, la montre, et non les écouteurs ou l'enceinte), puis lancer le processus d'association pour s'y connecter.
Deuxièmement, la vérification de la présence.
L'application doit savoir quand l'appareil est à proximité et quand il ne l'est plus.
Et lorsque l'appareil apparaît, l'application doit pouvoir être réveillée pour pouvoir faire son travail, même si l'utilisateur ne l'a pas ouverte.
Troisièmement, la maintenance de la connexion.
Une fois connectées, les applications veulent que la connexion soit fiable et persistante.
Si la connexion est interrompue (par exemple, si l'utilisateur s'éloigne de l'appareil, puis revient), l'application doit se reconnecter automatiquement, même si elle n'est pas en cours d'exécution à ce moment-là.
Quatrièmement, la synchronisation et le transfert des données.
C'est là que réside la véritable valeur.
Synchronisation des relevés des capteurs, envoi de mises à jour du micrologiciel ou envoi de paramètres à l'appareil.
Pour les dispositifs médicaux, cela peut être crucial.
Une mesure manquée peut signifier une alerte manquée.
Tous ces éléments nécessitent que le processus de l'application soit actif et connecté au bon moment.
Alors, comment les applications ont-elles répondu à tous ces besoins ?
Traditionnellement, les applications s'appuyaient fortement sur les services de premier plan.
Voici une implémentation classique.
Si vous avez déjà créé une application associée, cela vous semblera peut-être familier.
Vous déclarez les autorisations de recherche et de connexion Bluetooth, les autorisations de service de premier plan et le type de service de premier plan de l'appareil connecté, puis l'autorisation de notification, car un service de premier plan doit afficher une notification persistante.
<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" />Vous exécutez ensuite un service persistant pour maintenir le processus de l'application actif.
class BleForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
…
return START_STICKY
}
}Cette solution fonctionne, mais elle consomme de la batterie et de la mémoire, car le service s'exécute en permanence, même lorsque l'appareil n'est pas à proximité.
C'est pourquoi nous avons créé Companion Device Manager, ou CDM.
Tout d'abord, elle peut réactiver votre application en fonction de la présence de l'appareil. Tant que l'appareil reste à portée, le système maintient le processus de votre application actif.
Aucun service de premier plan n'est nécessaire.
Deuxièmement, CDM est compatible avec les profils d'appareil.
Avec un profil d'appareil, l'utilisateur accorde toutes les autorisations dont votre application a besoin en un seul geste, au lieu d'avoir à accepter une série de boîtes de dialogue d'autorisation.
Cela améliore considérablement l'expérience utilisateur et le taux de conversion.
Le profil de montre est disponible depuis Android 12, et celui de lunettes depuis Android 14.
Dans Android 17, nous avons ajouté deux nouveaux profils spécialement pour les dispositifs médicaux et les bracelets d'activité sport.
Il existe également des profils privilégiés réservés aux applications système.
Si nous constatons un besoin plus large de la part de l'écosystème, nous pourrons envisager de les ouvrir à davantage de développeurs.
L'utilisation de CDM est très simple.
Prenons l'exemple d'un dispositif médical.
Tout d'abord, déclarez les autorisations nécessaires : une pour le dispositif médical, une pour l'exécution en arrière-plan et une pour observer la présence de l'appareil.
<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" />Notez que contrairement aux autorisations que nous venons de voir pour l'implémentation des services de premier plan, toutes les autorisations listées ici sont des autorisations normales. Cela signifie qu'elles seront accordées lors de l'installation, sans boîte de dialogue.
L'utilisateur n'a qu'à approuver l'association.
Voyons comment créer cette association.
Ici, nous créons une requête d'association avec un filtre d'appareil pour que seul l'appareil approprié s'affiche.
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 sur "true", car nous ne voulons qu'une seule correspondance, et définissez le profil sur "medical".
Appelez ensuite "associate".
Le CDM gère l'analyse.
Dans onAssociationPending, nous obtiendrons un IntentSender.
Lancez-la. Le système affiche une boîte de dialogue d'association.
Avec un profil médical, un simple appui ici permet non seulement d'approuver l'association, mais aussi d'accorder toutes les autorisations nécessaires au profil, même en contournant le mode Ne pas déranger, ce qui est essentiel pour les alertes critiques.
Une fois que l'utilisateur a appuyé sur "Autoriser", nous recevons onAssociationCreated avec AssociationInfo, qui inclut un ID d'association.
Nous pouvons ensuite observer la présence de l'appareil en appelant startObservingDevicePresence avec cet ID d'association.
Vous devez également implémenter un service qui étend 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) { … }
}Lorsque l'appareil est à portée, le système se lie à votre service, même si votre application n'est pas en cours d'exécution.
Le système appellera onDevicePresenceEvent avec EVENT_BLE_APPEARED.
Vous pourrez ensuite vous connecter et synchroniser vos données.
Lorsqu'il quitte la zone, vous recevez EVENT_BLE_DISAPPEARED.
Vous pouvez ensuite effectuer le nettoyage.
Pas de service toujours actif ni de notification juste pour rester actif.
Il faut toutefois admettre que CDM présente quelques limites que les développeurs doivent garder à l'esprit.
Tout d'abord, les versions d'OS.
La plupart des fonctionnalités du CDM ont été introduites dans Android 12 et 13.
Mais des améliorations intéressantes ont été apportées dans Android 16 et les nouveaux profils dans Android 17.
Vérifiez donc la version de l'OS au moment de l'exécution et prévoyez une solution de remplacement pour les versions plus anciennes.
Cela dit, le SDK cible augmente chaque année, et le SDK minimal a tendance à suivre.
Pour faire face à la pression de mémoire à laquelle les applications sont confrontées actuellement et seront confrontées à l'avenir, nous vous encourageons vivement à migrer dès que possible du service de premier plan vers le CDM.
Si vous avez un cas d'utilisation qui n'est pas compatible avec CDM aujourd'hui, n'hésitez pas à nous en faire part.
Deuxièmement, CDM est actuellement compatible avec l'analyse en arrière-plan, mais pas avec l'annonce en arrière-plan.
Nous recevons rarement des demandes en ce sens.
Toutefois, si votre application nécessite que le téléphone agisse comme un périphérique et diffuse des annonces en continu en arrière-plan, nous serions ravis d'en savoir plus.
Troisièmement, CDM utilise le mode de détection à faible consommation d'énergie.
Il sacrifie un peu de réactivité en temps réel au profit de la batterie et de la mémoire.
Ne vous attendez donc pas à des réveils instantanés.
Enfin, aucune inspection de la charge utile n'est effectuée.
Vous ne pouvez pas vous enregistrer pour une caractéristique ou des données publicitaires spécifiques.
Mais une fois que l'appareil est à portée, votre application est réactivée.
Il peut ainsi se connecter et vérifier la charge utile si nécessaire.
Voilà tout ce qu'il faut savoir sur les applications associées et le CDM.
Je vais laisser la parole à Nataraj pour les applications VoIP.
CompanionDeviceManager est l'outil idéal si votre application possède un périphérique dédié et communique en permanence avec lui.
Bonjour !
Je m'appelle Nataraj K R.
Je vais maintenant parler du prochain scénario multi-appareil, à savoir les appels en temps réel.
Aujourd'hui, la façon dont les gens gèrent les appels évolue.
Plus de 40 % des appels sont passés sur des réseaux VoIP.
Et Android joue un rôle majeur dans cette évolution.
En fait, 71 % de ces appels sont passés sur des appareils Android, soit un appel sur deux.
Il ne s'agit pas seulement des appels.
L'écosystème d'appareils que les utilisateurs possèdent se développe également de manière massive.
Plus de 250 millions de voitures équipées d'Android Auto sont en circulation, et nous fournissons également plus d'un milliard d'appareils Bluetooth aux utilisateurs.
Les écouteurs, les montres Wear OS, les voitures Android Auto et les enceintes Bluetooth sont autant d'appareils que les utilisateurs possèdent en plus de leur téléphone, et ils veulent pouvoir passer leurs appels sur tous ces appareils de manière fluide.
Par exemple, un utilisateur reçoit un appel et regarde sa montre pour savoir qui l'appelle.
Il répond à l'appel sur sa montre.
Ils coupaient ou réactivaient le son selon les besoins, et ils mettaient leurs écouteurs pour continuer leur appel.
S'ils veulent sortir, ils montent dans leur voiture et s'attendent à ce que leur appel soit transféré de manière fluide vers l'habitacle de la voiture.
C'est donc devenu une routine normale pour l'utilisateur, et ce n'est pas une expérience premium.
Pour un utilisateur, il s'agit d'une exigence de base, et il nous incombe, en tant que développeurs Android, de fournir cette expérience fluide aux utilisateurs dans nos applications VoIP.
Mais si vous essayez de tout créer vous-même à partir de zéro, vous serez confronté à trois problèmes majeurs.
Premièrement, la fragmentation avec les sockets Bluetooth.
Comme le système ne sait pas que votre application gère un appel, vous devriez créer des sockets Bluetooth et des sessions multimédias personnalisés pour que votre appel puisse être projeté sur la montre ou sur Android Auto.
Le deuxième problème est la sélection audio.
Comme vous devez gérer des API AudioManager et Bluetooth de bas niveau, des fuites audio se produisent, et ce n'est pas une priorité pour le système, n'est-ce pas ?
Votre application passe un appel VoIP. Ainsi, chaque fois qu'un appel d'opérateur arrive, il est interrompu et le modem prend le contrôle de l'audio, ce qui laisse votre application sans audio.
Pire encore, même si l'appel mobile se termine, la sélection audio ne sera pas renvoyée à votre application.
Le troisième problème est la synchronisation.
L'utilisateur appuyait sur "Couper le son" sur sa montre, mais l'UI du téléphone indiquait toujours que le son de l'appel était activé.
Cela crée une grande confusion.
L'utilisateur raccroche depuis la colonne de direction de sa voiture, mais le téléphone affiche toujours une notification d'appel en cours.
C'est très frustrant pour l'utilisateur quand cela se produit.
Pour résoudre tous ces problèmes, nous avons créé Jetpack Telecom.
Vous enregistrez donc votre appel une seule fois auprès de Jetpack Telecom à l'aide de CallsManager, et Jetpack Telecom agit comme un coordinateur central des appels système.
Il se charge de projeter votre appel sur la montre, Android Auto et les autres surfaces distantes de l'utilisateur.
Il gère également la priorité audio.
Et surtout, tout cela fonctionne de manière fluide, jusqu'à Android 8.
Projeter l'appel sur l'écran de la montre ou de la voiture n'est qu'une partie de l'histoire, car l'utilisateur souhaite évidemment contrôler l'état de l'appel depuis ces appareils.
Il souhaite pouvoir couper le son de l'appel depuis sa montre ou le réactiver si nécessaire.
Il souhaite raccrocher l'appel depuis la colonne de direction de sa voiture ou en appuyant deux fois sur ses écouteurs.
Jetpack Telecom écoute donc ces événements matériels et les présente sous forme de rappels au même endroit, à savoir CallControlScope.
Il vous suffit d'écouter ces rappels, de gérer votre moteur d'appel et de mettre à jour l'état de l'appel en conséquence.
Jetpack Telecom se charge de synchroniser l'état de l'appel entre les appareils.
En arrière-plan, la bibliothèque renvoie les avantages dans deux buckets.
Premièrement, il résout évidemment tous les problèmes dont nous avons parlé.
Ainsi, au lieu de vous laisser gérer les API AudioManager de bas niveau, la bibliothèque fournit des points de terminaison d'appel déclaratifs, gère la priorité audio et synchronise les appareils.
Il se charge de transférer l'appel entre les appareils.
et gère également les appels cellulaires entrants.
Mais l'autre avantage de la bibliothèque est qu'elle est basée sur Kotlin. Chaque fois que vous ajoutez un appel avec Jetpack Telecom, un champ d'application de coroutine Kotlin s'ouvre.
Ainsi, il tire parti des coroutines Kotlin, et cette coroutine Kotlin reste active exactement jusqu'à la fin de l'appel.
Ainsi, tout job que vous avez exécuté en lien avec l'appel est automatiquement nettoyé dès que l'appel est déconnecté.
Vous n'auriez donc plus de jobs non autorisés, et le cycle de vie des appels serait également géré par la bibliothèque.
Maintenant, vous vous dites peut-être que cette bibliothèque est géniale et que vous voulez l'implémenter, n'est-ce pas ?
C'est aussi simple que cela.
La première étape consiste évidemment à déclarer la dépendance.
Ensuite, déclarez l'autorisation requise, à savoir MANAGE_OWN_CALLS, puis instanciez un objet CallsManager.
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />Après avoir instancié l'objet CallsManager, enregistrez votre application avec les fonctionnalités que vous souhaitez fournir, comme les appels vidéo ou le streaming d'appels.
// Initialize CallsManager & register supported capabilities once
val callsManager = CallsManager(context)
callsManager.registerAppWithTelecom(
capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
)Ensuite, l'étape 2.
Maintenant que vous avez enregistré votre application auprès de Jetpack Telecom, vous devez gérer les appels entrants et sortants.
Pour cela, vous devez créer un objet CallAttributesCompat, qui contient les métadonnées de l'appel, telles que le displayName de l'appelant ou de l'appelé, l'adresse, qui est le point de terminaison de l'appelant, la direction, qui indique s'il s'agit d'un appel entrant ou sortant afin que Jetpack Telecom puisse présenter l'UI correspondante sur ces surfaces distantes, et enfin, callType, s'il s'agit d'un appel audio ou vidéo afin que vous puissiez obtenir les ressources requises.
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
}Une fois l'objet de métadonnées prêt, ajoutez-le à l'aide de la méthode callsManager.addCall.
Cela ouvre un champ d'application de coroutine de contrôle des appels.
Une fois que vous avez ajouté votre appel, passez à l'étape 3.
L'étape 3 consiste à écouter les actions que l'utilisateur va effectuer à partir de ces surfaces distantes.
Comme vous disposez de CallControlScope, il vous suffit de collecter les flux 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)
}Vous collectez isMuted pour réagir chaque fois que l'utilisateur coupe le son de l'appel depuis ces surfaces distantes et, par conséquent, vous pouvez mettre à jour l'état de votre appel.
Collectez currentCallEndpoint pour savoir quel point de terminaison est actuellement utilisé pour l'appel afin de pouvoir mettre à jour votre UI en conséquence.
Vous pouvez afficher cet appareil spécifique dans l'interface utilisateur de votre téléphone.
Chaque fois que l'utilisateur souhaite passer d'un appareil à un autre, il lui suffit d'appeler la méthode requestEndpointChange avec le point de terminaison de son choix.
C'est aussi simple que cela.
Grâce à ces trois étapes, votre application est prête et a intégré Jetpack Telecom.
Mais ce n'est pas tout.
Avec Jetpack Telecom 1.1.0, nous proposons de nombreuses autres fonctionnalités, comme l'historique des appels unifié. Cela signifie que tous vos appels VoIP s'afficheront désormais de manière native dans l'outil de numérotation du système.
L'avantage est que vos utilisateurs pourront instancier un rappel One Tap directement depuis l'application Téléphone de votre système dans votre application.
Vous n'avez donc pas besoin de rechercher votre application parmi toutes les autres.
Il leur suffit d'instancier le rappel à partir du numéroteur système auquel ils sont habitués.
Mais nous proposons également des commandes précises.
Si vous ne souhaitez pas qu'un appel particulier soit enregistré dans le système, il vous suffit de définir l'indicateur isLogExcluded sur "true" dans l'objet CallAttributesCompat.
Il vous suffit de définir l'objet de métadonnées dont j'ai parlé précédemment sur "true". Votre appel ne sera pas enregistré dans le système et n'apparaîtra pas dans le numéroteur du système.
La deuxième fonctionnalité intéressante de la version 1.1.0 est la segmentation du réseau 5G.
Sur les réseaux d'opérateurs compatibles, les réseaux vous fournissent des tranches de réseau premium. Cela signifie que pour un cas d'utilisation particulier, l'opérateur vous fournit une tranche de réseau à faible latence et sans interruption, ce qui vous permet de bénéficier d'une meilleure qualité et d'une meilleure expérience.
Sur les réseaux d'opérateurs compatibles, vous bénéficierez donc de cette fonctionnalité sans aucune configuration requise de votre part, pas même une seule ligne de code.
Une fois que vous aurez intégré la bibliothèque, vos appels profiteront de ces tranches de réseau, et vous bénéficierez d'une meilleure latence et de meilleures performances.
Toutefois, si vous ne souhaitez pas utiliser cette fonctionnalité, vous pouvez la désactiver à l'aide de l'option CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.
Jetpack Telecom a déjà été implémenté par les principales applications de communication mondiales telles que Google Meet, WhatsApp, KakaoTalk et Zoho Arattai.
C'est maintenant à vous d'intégrer cette bibliothèque dans votre application et d'offrir une meilleure expérience utilisateur.
Maintenant que nous avons vu les appels en temps réel, je vais laisser la parole à mon formidable collègue Yacine pour vous parler de la fonctionnalité Rapprocher pour partager.
Merci.
Je sais que vous êtes un peu fatigués, mais ne vous inquiétez pas, c'est la dernière section.
Je vais donc parler du partage de fichiers pair à pair, mais d'autres cas d'utilisation sont également possibles avec le partage pair à pair.
Dans l'écosystème mobile, nous avons constaté que plus de 14 milliards de photos et de vidéos sont partagées chaque jour sur les réseaux sociaux et les applications de chat, ce qui est énorme.
Et la plupart du temps, tous ces fichiers sont partagés via le cloud. Vous prenez la photo, vous l'importez via votre service préféré, puis la personne doit la télécharger de la même manière de l'autre côté.
C'est la même chose lorsque vous essayez d'inviter un ami sur un réseau social, ou d'inviter quelqu'un à collaborer sur une playlist ou un document. Vous devez généralement saisir son nom d'utilisateur ou son adresse e-mail, ce qui prend un peu de temps sur un clavier.
Vous pourriez faire une erreur, et ce n'est pas une expérience très agréable, surtout quand vous êtes assis l'un à côté de l'autre. Vous vous dites : "Oh, je dois envoyer via le cloud, je dois attendre."
Ce n'est pas l'idéal.
Aujourd'hui, nous avons tous des téléphones, des appareils, des tablettes et des pliables sophistiqués, et nous prenons de superbes photos avec.
Mais lorsque vous les partagez sur certains de ces réseaux sociaux ou certaines de ces applications de chat, elles sont compressées, évidemment pour des problèmes de performances et de stockage. Au final, sur votre superbe écran, elles finissent par être assez pixélisées ou ne sont tout simplement pas de la meilleure qualité.
Ce n'est pas l'expérience que vous attendez d'un appareil haut de gamme.
Deuxièmement, les fichiers sont désormais assez volumineux.
Une seule photo peut facilement faire 10 Mo, et une vidéo de cinq minutes, 500 Mo. L'importation et le téléchargement de ces fichiers prennent donc un certain temps.
Enfin, de l'autre côté, vous devez attendre que cette latence soit écoulée.
Par exemple, vous pouvez monter dans un bus bondé et, pendant qu'il roule, vous n'avez pas la meilleure latence ni la meilleure couverture réseau. Parfois, le transfert de fichiers est interrompu, parfois, en raison de cette latence très longue, vous pouvez avoir un délai d'expiration et le transfert de fichiers est annulé, ce qui est vraiment frustrant pour un utilisateur.
Alors, au lieu de compter sur le cloud pour partager tous ces fichiers et de devoir attendre, pourquoi ne pas utiliser un réseau local, comme le Bluetooth ou le Wi-Fi ?
Mais si vous essayez de le faire vous-même, vous devez évidemment gérer le gestionnaire Bluetooth, le gestionnaire Wi-Fi et écrire votre propre protocole. Tout le monde n'a pas forcément envie de passer du temps à faire cela.
Mais en fait, il existe déjà quelque chose sur Android.
Il est déjà disponible, et vous en avez peut-être entendu parler : Quick Share.
Quick Share sur Android vous permet de disposer de cette abstraction déjà en place dans le système. Vous pouvez simplement partager des fichiers avec un autre appareil sans avoir à gérer tout cela.
Elle est déjà disponible dans votre feuille de partage et elle choisit le meilleur protocole pour votre appareil entre les deux appareils. Elle est disponible sur Marshmallow et les versions ultérieures, ce qui représente aujourd'hui une audience considérable, n'est-ce pas ?
Enfin, l'interface et l'expérience utilisateur familières que vous voyez sur le côté aident vraiment vos utilisateurs.
Ils connaissent donc l'interface utilisateur et n'ont pas besoin d'apprendre à s'en servir, car ils l'ont déjà vue par le passé.
Mais nous savons que les développeurs veulent aussi être maîtres de l'expérience.
Vous connaissez votre application et vos utilisateurs mieux que nous.
Alors, pourquoi ne pas intégrer Quick Share directement dans votre application ?
Nous avons donc commencé à discuter avec les développeurs et nous avons constaté que Quick Share était déjà très répandu.
Il est disponible sur Android, sur les Chromebooks, récemment sur Google Livres et également sur Windows, soit plus de trois milliards d'appareils, ce qui est énorme.
Et avec le Pixel 11, nous avons également introduit la fonctionnalité Rapprocher pour partager.
Ainsi, au lieu de devoir choisir l'appareil avec lequel vous souhaitez partager, il vous suffit d'appuyer sur l'écran. Grâce au NFC, nous nous chargeons de la visibilité directement pour vous.
Vous pouvez donc sélectionner une photo, mais aussi un fichier. Il vous suffit de rapprocher deux appareils pour les partager directement.
Et encore une fois, nous savons que les développeurs veulent contrôler l'expérience.
Nous savons que c'est le meilleur moyen pour que vos utilisateurs puissent utiliser une fonctionnalité spécifique, en l'occurrence le partage de fichiers.
Alors, pourquoi ne pas intégrer Quick Share à votre application ?
C'est pourquoi, l'année prochaine, nous vous proposerons directement le SDK Quick Share.
Vous pourrez l'utiliser directement dans votre application, et elle intègre déjà la sécurité, sous la forme d'un chiffrement de bout en bout entre les deux appareils.
Comme les transferts se font entre ces réseaux locaux, vous bénéficiez d'une vitesse maximale, évidemment sur votre réseau Wi-Fi domestique, mais aussi entre vous deux. Vous pouvez simplement créer un point d'accès automatiquement, sans avoir à vous en soucier.
Et pour les appareils compatibles avec Wi-Fi Direct, il est déjà inclus.
Et les fichiers que vous avez pris avec votre superbe appareil photo conserveront leur résolution et leurs détails optimaux, ce qui vous permettra de les voir parfaitement sur votre grand écran.
Et comme elle s'appuie sur les réseaux locaux, vous pouvez l'utiliser dans le métro, même dans les parties les plus profondes, car vous aurez toujours un réseau pour partager avec votre ami.
Dans l'avion, vous serez à des milliers de mètres d'altitude, dans le ciel, et vous pourrez toujours partager ces fichiers.
Enfin, pour vous en tant que développeur, cela représente une énorme économie en termes de données, car d'une part, vos utilisateurs n'ont pas besoin d'utiliser leur forfait de données, ce qui est très important lorsqu'ils voyagent en tant que touristes.
De plus, vous n'avez pas à gérer vos propres serveurs.
L'année dernière, nous avons annoncé aux développeurs et aux utilisateurs qu'avec Quick Share, ils pourraient partager des fichiers avec des iPhone via AirDrop.
Avec Quick Share, nous proposons la même chose aux développeurs.
Nous allons proposer un SDK Quick Share pour iOS afin que les développeurs d'applications iOS puissent partager des fichiers entre les deux plates-formes, quel que soit l'appareil.
Maintenant que nous avons vu la présentation, examinons rapidement le code.
Nous avons essayé de le rendre aussi simple que possible.
Vous lancez évidemment un QuickShareClient, vous devez joindre les fichiers que vous souhaitez partager, puis une fois la session démarrée, vous verrez cette magnifique fenêtre modale sur le côté, la bottom sheet où les utilisateurs pourront voir les fichiers envoyés sous forme d'aperçus. Si vous considérez que le contenu est sensible, vous pouvez également activer cette option à l'écran.
// 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)Lorsque vous souhaitez recevoir un fichier, toujours dans votre application, c'est la même chose, bien sûr. Dans ce cas, vous n'ajoutez pas de pièce jointe, et dès que la session est lancée, vous n'avez plus à vous en soucier.
// 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)Jusqu'à présent, vous n'avez vu que 20 lignes de code de chaque côté.
Nous savons qu'avec Quick Share et la fonctionnalité "rapprocher pour partager", la possibilité de partager des fichiers en rapprochant simplement les appareils permet également un nouveau cas d'utilisation.
Votre application peut être fermée sur l'autre appareil. En appuyant sur l'écran, dès que le fichier est transféré, votre application s'ouvre automatiquement et vous recevez l'URI pour pouvoir effectuer un traitement sur l'appareil, par exemple, ou afficher quelque chose à l'utilisateur directement dans votre application.
// 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)Encore une fois, vous n'avez pas besoin d'ouvrir votre application sur l'appareil récepteur.
Avec la fonctionnalité Rapprocher pour partager, elle s'ouvrira automatiquement.
Et bien sûr, si vous souhaitez que votre UI réagisse à ce transfert de fichiers, il vous suffit d'utiliser un flux Kotlin, et vous disposerez de tous les événements nécessaires pour le traiter.
// 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")
}
}
}Nous avons vu beaucoup de choses aujourd'hui, alors je vais conclure.
Et ne vous inquiétez pas, vous aurez quelques minutes pour faire une pause.
CompanionDeviceManager est la meilleure API pour gérer les interactions Bluetooth, y compris les interactions Bluetooth continues, comme Hanrui l'a présenté plus tôt.
Si votre application propose des appels VoIP, il ne s'agit pas nécessairement de son cas d'utilisation principal. Par exemple, votre application peut inclure une section d'assistance client permettant à l'utilisateur d'appeler l'assistance.
Dans ce cas, encore une fois, la meilleure façon de gérer les appels VoIP sur Android est d'utiliser Jetpack Telecom.
Enfin, pour partager des fichiers en peer-to-peer ou ouvrir une application, comme je l'ai mentionné, avec la fonctionnalité "Rapprocher pour partager", Quick Share est la solution.
Merci à tous. Avant de partir, juste une petite chose : nous serons dehors après la présentation pour répondre à vos questions.
Nous tenons des permanences si vous avez des questions sur les cas d'utilisation que nous avons présentés.
Et n'oubliez pas de partager ce que vous avez appris lors de votre présentation ou d'autres sessions, évidemment, sur les réseaux sociaux.
Nous avons l'#AndroidDevSummit.
Encore une fois, il est très important pour nous de savoir que nous suscitons de l'engagement et que les participants apprécient vraiment l'événement.
Cela nous aide à savoir que nous avons une communauté que nous voulons absolument revoir.
Récupérez votre breloque sociale au bureau d'information pour l'ajouter à votre porte-clés Android Bot.
Est-ce que j'ai bien fait ?
Merci.
<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")
}
}
}