Apps foundation

Habilitar interacciones fluidas entre dispositivos

Bocinas

Acerca de

Acerca de la charla

A medida que los usuarios alternan entre teléfonos, wearables y periféricos de audio, ofrecer experiencias fluidas en varios dispositivos se volvió esencial para la participación. Sin embargo, depender de enfoques de redes estándar suele provocar el agotamiento de la batería, picos de latencia y sesiones de usuario fragmentadas.

En esta prueba de etiquetas, se presentan las APIs de Android especializadas que se crearon para la comunicación multidispositivo, que ofrecen patrones eficientes en términos de memoria y energía para que las transiciones entre dispositivos sean instantáneas, confiables e intuitivas.

Resumen

  • Companion Device Manager reemplaza los servicios en primer plano para los periféricos Bluetooth, ya que activa las apps según la presencia y administra las conexiones sin agotar la batería. Android 17 agrega nuevos perfiles específicamente para dispositivos médicos y monitores de actividad física.
  • Jetpack Telecom actúa como un coordinador de llamadas del sistema para apps de VoIP en dispositivos conectados, como relojes y vehículos, y funciona desde Android 8. También sincroniza las acciones de silenciar y colgar a través de CallControlScope.
  • Entre las próximas funciones de Jetpack Telecom 1.1.0, se incluyen los historiales de llamadas unificados en el marcador del sistema (SDK 36.1 y versiones posteriores) y la división automática de redes 5G en las redes de operadores compatibles.
  • El SDK de Quick Share se lanzará el próximo año para que los desarrolladores puedan incorporar el uso compartido de archivos locales con encriptación de extremo a extremo directamente en las apps para iOS y Android. La función Acercar para compartir (lanzada con Pixel 11) puede incluso activar automáticamente una app receptora cerrada.

Recursos

Gemini Notebook

Temas

  • multidispositivo
  • companion-device-manager
  • jetpack-telecom
  • quick-share
  • bluetooth
  • voip
  • entre pares

Transcripción

Descargar transcripción

Introducción a las interacciones multidispositivo

  • ¡Hola!

  • Te damos la bienvenida al módulo Habilita interacciones fluidas entre dispositivos.

  • Soy Hanrui y hoy me acompañan mis colegas Yacine y Nataraj.

  • Cuando hablamos de conectividad, lo que suele venir a la mente es un vínculo de red básico o auriculares Bluetooth.

  • En Android, la conectividad es mucho más amplia que eso.

  • En realidad, se trata de hacer que los dispositivos funcionen mejor en conjunto.

  • Hoy, analizaremos situaciones comunes de varios dispositivos y te mostraremos qué API es la adecuada para cada una.

  • Durante mucho tiempo, solíamos compilar apps para un solo dispositivo, solo para teléfonos.

  • Luego, se popularizaron los wearables.

  • Pronto, se volvió muy común tener un reloj inteligente vinculado con un teléfono, sincronizar datos en segundo plano o recibir llamadas directamente.

  • Agrega auriculares inalámbricos.

  • Ahora, los usuarios cambian de dispositivo de audio durante una llamada, del reloj al teléfono, a los auriculares o al auto.

  • Más recientemente, muchos usuarios también llevan un segundo dispositivo de pantalla grande, como un plegable o una tablet.

  • Esperan que la app funcione sin problemas en estos dispositivos.

  • Y eso se extiende a las laptops y las Chromebooks, donde los usuarios pueden ejecutar apps para Android y mantenerse conectados directamente desde su escritorio.

  • Incluso en 2023, un usuario promedio tenía entre tres y cuatro dispositivos.

  • Los usuarios esperan más que solo una funcionalidad independiente en estos dispositivos.

  • Buscan una experiencia de interconectividad extraordinariamente mejor.

  • Esa experiencia de integración es especialmente clara cuando las personas están en la misma habitación, como nosotros hoy, y quieren una forma sencilla de compartir fotos, vínculos o apps con las personas que están a su lado.

  • Si observamos las cifras, el ecosistema es enorme y está creciendo rápidamente.

  • En este año, hay alrededor de 22,000 millones de dispositivos de IoT en todo el mundo, con un crecimiento interanual del 14%.

  • Entre todos estos extremos, el Wi-Fi representa un tercio y el Bluetooth, un cuarto.

  • La conectividad multidispositivo ya no es un caso extremo.

  • Forma parte de nuestra vida cotidiana.

  • Sin embargo, hoy en día, la mayoría de las apps aún compilan sus funciones multidispositivo en APIs de red y Bluetooth de bajo nivel, lo que genera tres desafíos.

  • Primero, la batería y la memoria.

  • Mantener en ejecución los análisis y servicios en segundo plano agota la batería y la memoria.

  • Segundo, la latencia.

  • Los retrasos impredecibles interrumpen las llamadas y la sincronización de datos en tiempo real.

  • Y, en tercer lugar, la fricción de conexión.

  • Administrar el descubrimiento, la vinculación y la reconexión en todos los dispositivos es complejo y puede generar fácilmente una experiencia del usuario deficiente.

  • Para evitar esos dolores de cabeza, creamos APIs específicas en tres áreas principales para que las funciones multidispositivo sean realmente sencillas.

  • Comenzaré con las apps complementarias, luego Nataraj hablará sobre las apps de VoIP y, por último, Yacine hablará sobre el uso compartido de P2P.

Descripción general de las apps complementarias

  • Primero, analicemos las apps complementarias.

  • Una app complementaria es una app que instalas en tu teléfono para que un dispositivo complementario, como un reloj inteligente o un sensor de glucosa, pueda funcionar bien con él.

  • En esta sección, nos enfocaremos en los dispositivos conectados por Bluetooth.

  • Por lo general, hay cuatro categorías de dispositivos complementarios.

  • Primero, los dispositivos médicos, como los sensores de glucosa y los monitores de signos vitales.

  • Y wearables, como relojes inteligentes y pulseras de fitness.

  • Auriculares con Vinculación rápida y controles inteligentes

  • Y kits para automóviles, para funciones como llaves digitales para automóviles y llamadas con manos libres.

  • Cada categoría tiene sus propios recorridos del usuario clave, pero todos dependen de una conexión confiable entre el dispositivo y el teléfono.

  • En el caso de los dispositivos médicos, esa conexión puede ser incluso de vital importancia.

  • Analicemos cuatro necesidades comunes que vemos en estas categorías.

  • Primero, la administración de dispositivos.

  • La app debe encontrar el dispositivo correcto entre todos los que están cerca, como el reloj, no los auriculares ni la bocina, y, luego, iniciar el flujo de vinculación para conectarse.

  • En segundo lugar, la verificación de presencia.

  • La app necesita saber cuándo el dispositivo está cerca y cuándo no.

  • Y cuando el dispositivo aparece, la app necesita una forma de activarse para poder hacer su trabajo, incluso si el usuario no la abrió.

  • Tercero, mantenimiento de la conexión.

  • Una vez conectadas, las apps quieren que la conexión sea confiable y persistente.

  • Si se interrumpe la conexión, por ejemplo, si el usuario sale del alcance y luego regresa, la app debe volver a conectarse automáticamente, incluso si no se está ejecutando en ese momento.

  • En cuarto lugar, la sincronización y transferencia de datos.

  • Aquí es donde está el verdadero valor.

  • Sincronizar las lecturas de los sensores, enviar actualizaciones de firmware o enviar parámetros de configuración al dispositivo

  • En el caso de los dispositivos médicos, esto puede ser fundamental.

  • Una lectura perdida puede significar una alerta perdida.

  • Todos estos requieren que el proceso de la app esté activo y conectado en el momento adecuado.

  • Entonces, ¿cómo han satisfecho las apps todas estas necesidades?

  • Tradicionalmente, las apps dependían en gran medida de los servicios en primer plano.

  • Esta es una implementación típica.

  • Si alguna vez creaste una app complementaria, es posible que esto te resulte familiar.

  • Declaras los permisos de búsqueda de dispositivos Bluetooth y conexión, los permisos de servicio en primer plano y el tipo de servicio en primer plano del dispositivo conectado, y, luego, el permiso de notificaciones, ya que un servicio en primer plano debe mostrar una notificación 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" />
  • Luego, ejecutas un servicio persistente para mantener activo el proceso de la app.

    class BleForegroundService : Service() {
       override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          …
          return START_STICKY
       }
    }
  • Esto funciona, pero consume batería y memoria porque el servicio se ejecuta todo el tiempo, incluso cuando el dispositivo no está cerca.

  • Por eso, creamos Companion Device Manager, o CDM para abreviar.

  • En primer lugar, puede activar tu app en función de la presencia del dispositivo y, mientras el dispositivo permanezca dentro del alcance, el sistema mantendrá activo el proceso de tu app.

  • No se necesita un servicio en primer plano.

  • En segundo lugar, el CDM admite perfiles de dispositivos.

  • Con un perfil de dispositivo, el usuario otorga todos los permisos que necesita tu app con un solo toque, en lugar de una cadena de diálogos de permisos.

  • Esto mejora significativamente la experiencia del usuario y el porcentaje de conversiones.

  • El perfil de reloj está disponible desde Android 12, y el de lentes, desde Android 14.

  • En Android 17, agregamos dos perfiles nuevos específicamente para dispositivos médicos y monitores de estado físico.

  • Actualmente, también hay algunos perfiles con privilegios solo para las apps del sistema.

  • Si detectamos una necesidad más amplia en el ecosistema, es posible que consideremos abrirlas a más desarrolladores.

Implementación con Companion Device Manager

  • Usar CDM es muy fácil.

  • Usemos un dispositivo médico como ejemplo.

  • Primero, declara los permisos necesarios: uno para el dispositivo médico, uno para ejecutar en segundo plano y otro para observar la presencia 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" />
  • Un aspecto que se debe destacar, a diferencia de los permisos que acabamos de ver con la implementación del servicio en primer plano, es que todos los permisos que se indican aquí son normales, lo que significa que se otorgarán durante la instalación, sin un diálogo de solicitud.

  • Lo único que debe aprobar el usuario es la asociación.

  • Veamos cómo crear esa asociación.

  • Aquí, creamos una solicitud de asociación con un filtro de dispositivo para que solo aparezca el dispositivo correcto.

    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 en verdadero porque solo queremos una coincidencia y establece el perfil en médico.

  • Luego, llama a associate.

  • El CDM controla el análisis.

  • En onAssociationPending, obtendremos un IntentSender.

  • Iníciala y el sistema mostrará un diálogo de asociación.

  • Con un perfil médico, este toque no solo aprueba la asociación, sino que también otorga todos los permisos que necesita el perfil, incluso omitiendo No interrumpir, lo que es fundamental para las alertas críticas.

  • Una vez que el usuario presione Permitir, se llamará a onAssociationCreated con AssociationInfo, que incluye un ID de asociación.

  • Luego, podemos observar la presencia del dispositivo llamando a startObservingDevicePresence con ese ID de asociación.

  • También debes implementar un servicio que extienda 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) { … }
    }
  • Cuando el dispositivo entra en el rango, el sistema se vincula a tu servicio, incluso si tu app no se está ejecutando.

  • Y el sistema llamará a onDevicePresenceEvent con EVENT_BLE_APPEARED.

  • Luego, podrás conectarte y sincronizar los datos.

  • Cuando el dispositivo salga, recibirás EVENT_BLE_DISAPPEARED.

  • Luego, puedes realizar la limpieza.

  • No hay un servicio siempre activo ni una notificación solo para mantenerse activo.

  • Ahora bien, debemos admitir que CDM tiene algunas limitaciones que los desarrolladores deben tener en cuenta.

  • Primero, las versiones de SO.

  • La mayoría de las funciones de CDM llegaron en Android 12 y 13.

  • Sin embargo, se incorporaron algunas mejoras interesantes en Android 16 y los nuevos perfiles en Android 17.

  • Por lo tanto, verifica la versión del SO en el tiempo de ejecución y mantén un resguardo para las versiones anteriores.

  • Dicho esto, el SDK de destino aumenta cada año y el SDK mínimo tiende a seguirlo.

  • Para abordar la presión de memoria que enfrentan las apps ahora y que enfrentarán en el futuro, recomendamos que las apps migren del servicio en primer plano al CDM lo antes posible.

  • Si tienes un caso de uso que CDM no puede admitir actualmente, infórmanos al respecto.

  • En segundo lugar, actualmente, CDM admite el análisis en segundo plano, pero no la publicidad en segundo plano.

  • Rara vez escuchamos que se necesite esto.

  • Sin embargo, si tu app necesita que el teléfono actúe como periférico y se anuncie continuamente en segundo plano, nos gustaría obtener más información al respecto.

  • En tercer lugar, CDM usa el modo de análisis de bajo consumo.

  • Intercambia un poco de capacidad de respuesta en tiempo real por batería y memoria.

  • Por lo tanto, no esperes que se active de inmediato.

  • Por último, no se puede ver la carga útil.

  • No puedes registrarte para recibir una característica o datos de anuncios específicos.

  • Sin embargo, una vez que el dispositivo está dentro del alcance, la app se activa.

  • Por lo tanto, puede conectarse y verificar la carga útil según sea necesario.

  • Eso es todo sobre las apps complementarias y el CDM.

  • Le daré la palabra a Nataraj para que hable sobre las apps de VoIP.

Desafíos de las llamadas de VoIP multidispositivo

  • CompanionDeviceManager es la herramienta adecuada si tu app tiene un periférico dedicado y se comunica continuamente a través de él.

  • Hola a todos.

  • Soy Nataraj K R.

  • Ahora, hablaré sobre el siguiente caso de uso multidispositivo, que son las llamadas en tiempo real.

  • En la actualidad, la forma en que las personas manejan las llamadas está cambiando.

  • Más del 40% de las llamadas se realizan a través de redes de VoIP.

  • Y Android desempeña un papel importante en eso.

  • De hecho, el 71% de esas llamadas se realizan en dispositivos Android, lo que equivale a una de cada dos llamadas.

  • No se trata solo de las llamadas.

  • El ecosistema de dispositivos que tienen las personas también está creciendo de forma masiva.

  • Hay más de 250 millones de automóviles con Android Auto en las calles, y también enviamos más de mil millones de dispositivos Bluetooth a los usuarios.

  • Los auriculares, los relojes Wear OS, los vehículos con Android Auto y las bocinas Bluetooth son todos los dispositivos que los usuarios tienen alrededor del teléfono y quieren que sus llamadas fluyan por todos ellos sin problemas.

  • Por ejemplo, el usuario recibiría una llamada y miraría su reloj para conocer la información de quien llama.

  • Respondería la llamada en su reloj.

  • Silenciarían o activarían el sonido según fuera necesario, se pondrían los auriculares y continuarían con la llamada.

  • Si quieren salir, se subirían a su auto y esperarían que la llamada se transfiriera sin problemas al habitáculo del auto.

  • Por lo tanto, esto se convirtió en una rutina normal para el usuario, y no es una experiencia premium.

  • Para un usuario, este es un requisito básico, y los desarrolladores de Android debemos proporcionar esa experiencia fluida en nuestras apps de VoIP a los usuarios.

  • Pero si intentas crear todo esto por tu cuenta desde cero, terminarás con tres problemas importantes.

  • Uno, la fragmentación con sockets de Bluetooth.

  • Como el sistema no sabe que tu app está administrando una llamada, terminarías creando sockets de Bluetooth y sesiones multimedia personalizados para que tu llamada se pueda proyectar en el reloj o en Android Auto.

  • El segundo problema es el foco de audio.

  • Como tienes que lidiar con las APIs de AudioManager y Bluetooth de bajo nivel, se producen pérdidas de audio, y eso no es una prioridad para el sistema, ¿verdad?

  • Tu app tiene una llamada de VoIP, por lo que, cada vez que entra una llamada del operador, se interrumpe y el módem toma el audio, lo que deja a tu app sin audio.

  • Lo peor es que, cuando finalice la llamada de red móvil, el foco de audio no se devolverá a tu app.

  • Y el tercer problema es la sincronización.

  • El usuario presionaba el botón para silenciar en el reloj, pero la IU del teléfono seguía mostrando la llamada como no silenciada.

  • Esto genera mucha confusión.

  • El usuario desconectaba la llamada desde la columna de dirección del vehículo, pero el teléfono seguía mostrando una notificación de llamada en curso.

  • Es muy frustrante para el usuario cuando eso sucede.

Presentamos Jetpack Telecom

  • Para resolver todos estos problemas, creamos Jetpack Telecom.

  • Por lo tanto, registras tu llamada una vez con Jetpack Telecom usando CallsManager, y Jetpack Telecom actúa como un coordinador central de llamadas del sistema.

  • Se encarga de proyectar la llamada en el reloj, Android Auto y otras plataformas remotas que tenga el usuario.

  • También se encarga de controlar el foco de audio.

  • Y, lo más importante, todo esto funciona sin problemas, incluso en Android 8.

  • Proyectar la llamada en la pantalla del reloj o del auto es solo la mitad de la historia, ¿verdad?, porque el usuario obviamente quiere controlar el estado de la llamada desde estos dispositivos.

  • Quieren silenciar o activar el sonido de la llamada desde el reloj según sea necesario.

  • Querrán colgar la llamada desde la columna de dirección del auto o presionando dos veces los auriculares.

  • Por lo tanto, Jetpack Telecom escucha estos eventos de hardware y los presenta como devoluciones de llamada en un solo lugar, que es CallControlScope.

  • Solo debes escuchar estas devoluciones de llamada, controlar el motor de llamadas y actualizar el estado de la llamada según corresponda.

  • Jetpack Telecom se encarga de sincronizar el estado de la llamada entre los dispositivos.

  • Por lo tanto, de forma interna, la biblioteca ofrece beneficios en dos categorías.

  • En primer lugar, obviamente, resuelve todos los problemas de los que hemos hablado.

  • Por lo tanto, en lugar de que tengas que lidiar con las APIs de AudioManager de bajo nivel, la biblioteca proporciona extremos de llamadas declarativos, administra el foco de audio y se sincroniza entre los dispositivos.

  • Se encarga de transferir la llamada entre los dispositivos.

  • Y también se encarga de las llamadas de telefonía móvil.

  • Sin embargo, el otro beneficio que ofrece la biblioteca es que, como se basa en Kotlin, cada vez que agregas una llamada con Jetpack Telecom, se abre un permiso de corrutina de Kotlin.

  • De esta manera, se aprovechan las corrutinas de Kotlin, y esa corrutina de Kotlin permanece activa exactamente hasta que finaliza la llamada.

  • Por lo tanto, cualquier trabajo que hayas ejecutado con respecto a la llamada se limpia automáticamente en cuanto se desconecta la llamada.

  • Por lo tanto, no te quedarías con trabajos no autorizados, y la biblioteca también se encargaría del ciclo de vida de las llamadas.

  • Ahora, te preguntarás si esta biblioteca es genial y si quieres implementarla, ¿verdad?

  • Y es tan fácil como seguir tres pasos sencillos.

  • El paso 1 es, obviamente, declarar la dependencia.

  • Después, declara el permiso necesario, que es MANAGE_OWN_CALLS, y, luego, crea una instancia de un objeto CallsManager.

    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
  • Después de crear una instancia del objeto CallsManager, registra tu app con las funciones que quieras proporcionar con ella, como videollamadas o transmisión de llamadas.

    // Initialize CallsManager & register supported capabilities once
    val callsManager = CallsManager(context)
    
    callsManager.registerAppWithTelecom(
        capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                       CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
    )
  • Después, el paso número 2.

  • Ahora que registraste tu app en Jetpack Telecom, es momento de controlar las llamadas entrantes y salientes.

  • Para ello, debes compilar un objeto CallAttributesCompat, que contiene los metadatos de la llamada, como el displayName de quien llama o quien recibe la llamada, la dirección, que es el extremo de quien llama, la dirección, que especifica si la llamada es llamada entrante o llamada saliente para que Jetpack Telecom pueda presentar la IU correspondiente en estas plataformas remotas, y, luego, callType, si se trata de una llamada de audio o videollamada para que puedas obtener los recursos necesarios.

    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 vez que tengas listo el objeto de metadatos, agrégalo con el método callsManager.addCall.

  • Esto abre un alcance de corrutina de control de llamadas.

  • Una vez que hayas agregado tu llamada, pasemos al paso 3.

  • El paso 3 consiste en escuchar las acciones que el usuario realizará desde esas plataformas remotas.

  • Por lo tanto, como tienes CallControlScope, solo debes recopilar los flujos de 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)
    }
  • Recopilas isMuted para reaccionar cada vez que el usuario silencia la llamada desde esas plataformas remotas y, en consecuencia, puedes actualizar el estado de la llamada.

  • Recopila currentCallEndpoint para saber en qué extremo se está realizando la llamada y poder actualizar la IU según corresponda.

  • Puedes mostrar ese dispositivo en particular en la IU de tu teléfono.

  • Y cada vez que el usuario quiera cambiar de un dispositivo a otro, solo llama al método requestEndpointChange con el extremo preferido que el usuario quiera.

  • Es así de simple.

  • Con estos tres pasos, tu app estará lista para usar y tendrá integrado Jetpack Telecom.

  • Pero eso no es todo.

  • Con Jetpack Telecom 1.1.0, ofrecemos muchas más funciones, como historiales de llamadas unificados, lo que significa que todas tus llamadas de VoIP ahora aparecerán de forma nativa en el marcador del sistema.

  • La ventaja de esto es que tus usuarios podrán crear una instancia de devolución de llamada con un toque directamente desde el marcador del sistema en tu app.

  • Por lo tanto, no es necesario que busques tu app en particular entre todas las apps.

  • Solo pueden crear una instancia de devolución de llamada desde el marcador del sistema al que están acostumbrados.

  • Sin embargo, para algunos, pero si tú, pero también proporcionamos controles detallados.

  • Si no quieres que una llamada en particular se registre en el sistema, solo debes establecer la marca isLogExcluded como verdadera dentro del objeto CallAttributesCompat.

  • El objeto de metadatos del que hablé antes, simplemente lo configuras como verdadero y tu llamada no se registrará en el sistema y no aparecerá en el marcador del sistema.

  • La segunda función increíble que se incluirá en la versión 1.1.0 es la segmentación de red 5G.

  • Por lo tanto, en las redes de operadores compatibles, lo que sucede es que las redes te proporcionan porciones de red premium, lo que significa que, para un caso de uso en particular, el operador te dará una porción de red que tiene baja latencia y no tiene otras interrupciones, de modo que tendrás una mejor calidad y una mejor experiencia con ella.

  • Por lo tanto, en las redes de operadores compatibles, obtendrás esta función de inmediato, por lo que no se requiere ningún esfuerzo adicional de tu parte, ni siquiera una sola línea de código.

  • Una vez que integres la biblioteca, tus llamadas aprovecharán esas porciones de red y obtendrás una mejor latencia y un mejor rendimiento.

  • Sin embargo, si por algún motivo no quieres usar esta función, puedes inhabilitarla con la marca CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK.

  • Jetpack Telecom ya se implementó en las principales apps de comunicación globales, como Google Meet, WhatsApp, KakaoTalk y Zoho Arattai.

  • Ahora depende de ti integrar esta biblioteca en tu app y proporcionar esa experiencia mejorada y fluida para el usuario.

  • Ahora que ya hablamos de las llamadas en tiempo real, le daré la palabra a mi increíble colega Yacine para que hable de Tocar para compartir.

  • Gracias.

Desafíos del uso compartido entre pares

  • Muy bien, amigos, sé que están un poco cansados, pero no se preocupen, esta es la última sección.

  • Por lo tanto, voy a hablar sobre el uso compartido de archivos entre pares, pero también podemos usar el uso compartido entre pares para otros casos de uso.

  • En todo el ecosistema móvil, vemos que se comparten más de 14,000 millones de fotos y videos en redes sociales y apps de mensajería todos los días, lo que representa una cantidad enorme.

  • Y todos esos archivos se comparten la mayor parte del tiempo a través de la nube, por lo que tomas la foto, la subes a través de tu servicio favorito y, luego, la persona tiene que descargarla de la misma manera desde el otro lado.

  • Y lo mismo sucede cuando intentas invitar a alguien o a un amigo a una red social para que colabore en una playlist o un documento. Por lo general, tienes que escribir su nombre de usuario o su correo electrónico, lo que lleva un poco de tiempo en el teclado.

  • Podrías cometer un error, y esa no es una buena experiencia, especialmente cuando ambos están sentados uno al lado del otro, por lo que es como, oh, tengo que enviar a través de la nube, tengo que esperar.

  • No es muy bueno.

  • Y, hoy en día, todos tenemos teléfonos, dispositivos, tablets y plegables sofisticados, y tomamos fotos increíbles con ellos.

  • Pero cuando compartes contenido a través de algunas de esas redes sociales o algunas de estas apps de mensajería, obviamente, por problemas de rendimiento y almacenamiento, se comprimen, lo que, al final, en tu increíble pantalla, termina viéndose bastante pixelado o simplemente no con la mejor calidad.

  • Esa no es la mejor experiencia que te gustaría tener en tu dispositivo premium.

  • En segundo lugar, los archivos ahora son bastante grandes.

  • Una sola imagen puede pesar fácilmente 10 MB, y un video de 5 minutos, unos 500 MB, por lo que todo eso tarda bastante en subirse y descargarse.

  • Por último, en el otro extremo, debes esperar esa latencia.

  • A veces, puedes subirte a un autobús lleno de gente y, mientras se mueve, no tienes la mejor latencia ni la mejor cobertura de red, y la transferencia de archivos se interrumpe. A veces, debido a esa latencia tan larga, es posible que se agote el tiempo de espera y se cancele la transferencia de archivos, lo que es muy frustrante para un usuario.

  • Por lo tanto, en lugar de depender de la nube para poder compartir todos esos archivos y tener que esperar todo ese tiempo, ¿por qué no usar una red local, como Bluetooth o Wi-Fi?

  • Pero si intentas hacerlo por tu cuenta, obviamente, tienes que lidiar con el administrador de Bluetooth, el administrador de Wi-Fi, tener que escribir tu propio protocolo, es decir, no todo el mundo quiere dedicar tiempo a eso.

  • Pero, en realidad, ya hay algo en Android.

  • Ya está disponible y es posible que hayas oído hablar de ella: Quick Share.

Descripción general de Quick Share

  • Quick Share en Android te permite tener esta abstracción ya implementada en el sistema y puedes compartir archivos con otro dispositivo sin tener que administrar todo eso.

  • Ya está disponible en la hoja de contenido compartido y cambia entre los protocolos para elegir el mejor para tu dispositivo entre ambos dispositivos. Está disponible en Marshmallow y versiones posteriores, lo que, hoy en día, es un público bastante grande, ¿verdad?

  • Por último, esa IU y UX conocidas que ves en el lateral realmente ayudan a tus propios usuarios.

  • Por lo tanto, saben qué es la IU, no tienen que aprender a usarla, ya la han visto en el pasado.

  • Pero sabemos que los desarrolladores también quieren tener el control de la experiencia.

  • Tú conoces tu app y a tus usuarios mejor que nosotros.

  • Por lo tanto, ¿por qué no tener Quick Share directamente en tu app?

  • Por lo tanto, comenzamos a hablar con los desarrolladores y vimos que Quick Share ya está bastante extendido.

  • Está disponible en Android, en Chromebooks, recientemente en Google Books y también en Windows, es decir, en más de 3,000 millones de dispositivos, lo que es enorme.

  • Y con el Pixel 11, también incorporamos la capacidad de acercar para compartir.

  • Por lo tanto, en lugar de tener que elegir el dispositivo con el que quieres compartir, puedes simplemente acercar y, con NFC, realizamos esa visibilidad directamente por ti.

  • Por lo tanto, puedes seleccionar una foto, pero también un archivo, simplemente acercas dos dispositivos y se comparten directamente con un toque.

  • Y, de nuevo, sabemos que hay desarrolladores que quieren controlar la experiencia.

  • Sabemos que es la mejor manera de que tus usuarios puedan usar una función específica, en este caso, el uso compartido de archivos.

  • Entonces, ¿por qué no tener Quick Share integrado en tu app?

  • Por eso, el próximo año, te ofreceremos el SDK de Quick Share directamente.

  • Podrás usarla directamente en tu app y viene con seguridad integrada, como la encriptación de extremo a extremo entre ambos dispositivos.

  • Como las transferencias se realizan entre esas redes locales, tendrás la velocidad máxima, obviamente en la red Wi-Fi de tu casa, pero también entre ambos, ya que se puede crear un hotspot automáticamente, sin que tengas que intervenir.

  • Y, para los dispositivos que admiten Wi-Fi Direct, ya está incluido.

  • Y los archivos que hayas tomado con tu increíble cámara mantendrán su mejor resolución y detalles, para que puedas verlos bien en tu gran pantalla.

  • Y, dado que se basa en redes locales, puedes usarlo en el metro, incluso en la parte más profunda, y seguirás teniendo red para compartir con tu amigo.

  • En el avión, también podrás compartir esos archivos, no sé a cuántos miles de metros de altura, en el cielo.

  • Por último, para ti como desarrollador, es un gran ahorro de costos en términos de datos, porque, en primer lugar, tus usuarios no tienen que usar ese plan de datos, y es un gran problema cuando viajan como turistas.

  • Además, no tienes que mantenerlo en tus propios servidores.

  • Y el año pasado, les mencionamos a los desarrolladores y también a los usuarios que, con Quick Share, podrán compartir archivos con dispositivos iPhone a través de AirDrop.

  • Con Quick Share, ofrecemos lo mismo para los desarrolladores.

  • Tendremos un SDK de Quick Share para iOS para que los desarrolladores que tengan apps para iOS puedan compartir archivos entre ambas plataformas, independientemente del dispositivo.

  • Todo eso es una presentación elegante, ahora, echemos un vistazo rápido al código.

  • Intentamos que sea lo más sencillo posible.

  • Obviamente, inicias una QuickShareClient, tienes que adjuntar los archivos que quieres compartir y, luego, una vez que inicies la sesión, tendrás esta hermosa ventana modal que ves en el costado, la hoja inferior donde los usuarios podrán ver los archivos que se envían como vistas previas y, si consideras que el contenido es sensible, también puedes habilitarlo con la opción que se ve en la pantalla.

    // 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)
  • Cuando quieras recibir un archivo, de nuevo, dentro de tu app, lo mismo, por supuesto, en este caso, no estás agregando un archivo adjunto y, luego, en cuanto se inicia la sesión, no tienes que lidiar con nada de eso.

    // 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)
  • Hasta ahora, lo que viste son literalmente solo 20 líneas de código en ambos lados.

  • Sabemos que, con Quick Share y acercar para compartir, la posibilidad de compartir archivos con solo tocar los dispositivos también habilita un nuevo caso de uso.

  • Tu app podría cerrarse en el otro dispositivo y, cuando se presione, en cuanto se transfiera el archivo, tu app se abrirá automáticamente y recibirás el URI para poder realizar algún procesamiento en el dispositivo, por ejemplo, o mostrar algo al usuario directamente en tu 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)
  • Por lo tanto, no es necesario que abras la app en el dispositivo receptor.

  • Con la función de presionar para compartir, se abrirá automáticamente.

  • Y, por supuesto, si quieres que tu IU reaccione a esa transferencia de archivos, solo tienes un flujo de Kotlin y tendrás todos los eventos para que puedas procesarlo.

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

Resumen y conclusión

  • Ya vimos mucho hoy, así que voy a terminar.

  • No te preocupes, tendrás unos minutos para descansar.

  • CompanionDeviceManager es la mejor API para que controles cualquier interacción de Bluetooth, incluidas las continuas, como Hanrui presentó antes.

  • Si tu app tiene llamadas de voz por IP, no es necesario que sea su caso de uso principal, puede ser, por ejemplo, que tengas clientes de asistencia como parte de tu app donde el usuario pueda llamar al servicio de asistencia.

  • En este caso, de nuevo, la mejor manera de controlar las llamadas de voz sobre IP en Android es Jetpack Com… Jetpack Telecom.

  • Por último, para cualquier app de apertura o uso compartido de archivos de par a par, como mencioné, con la función de acercar para compartir, Quick Share es la solución.

  • Así que, gracias a todos, y antes de que se vayan, solo una cosa rápida, estaremos afuera después para responder sus preguntas.

  • Tenemos horarios de atención por si tienes alguna pregunta sobre todos los casos de uso que presentamos.

  • Y no olvides compartir lo que aprendiste en tu charla o en otras sesiones, obviamente, en las redes sociales.

  • Tenemos #AndroidDevSummit.

  • Nuevamente, es muy importante para nosotros saber que tenemos participación, que hay personas que realmente disfrutan del evento.

  • Nos ayuda a saber que definitivamente tenemos una comunidad que queremos volver a ver.

  • Y recoge tu dije de redes sociales que puedes agregar a tu llavero de bot de Android en el mostrador de información.

  • ¿Lo hice bien?

  • Gracias.

Código

Fragmentos de código

Fragmento de XML en el minuto 6:17 de "Descripción general de las apps complementarias"
<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" />
Fragmento de Kotlin en el minuto 6:34 de "Descripción general de las apps complementarias"
class BleForegroundService : Service() {
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      …
      return START_STICKY
   }
}
Fragmento XML en el minuto 08:03 de "Implementación con Companion Device Manager"
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_MEDICAL" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
Fragmento de Kotlin en el minuto 08:38 de "Implementación con Companion Device Manager"
val paringRequest = AssociationRequest.Builder()
   .addDeviceFilter(deviceFilter)
   .setSingleDevice(true)
   .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_MEDICAL)
   .build()

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

       override fun onAssociationCreated(associationInfo: AssociationInfo) { … }

       override fun onFailure(error: CharSequence?) { … }
   }
)
Fragmento de Kotlin en el minuto 09:47 de "Implementation using Companion Device Manager"
class BleCompanionDeviceService : CompanionDeviceService() {

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

   private fun onDeviceAppeared(associationId: Int) { … }

   private fun onDeviceDisappeared(associationId: Int) { … }
}
Fragmento de XML en el minuto 18:29 de "Presentamos Jetpack Telecom"
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Fragmento de Kotlin en el minuto 18:36 de "Presentamos 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
)
Fragmento de Kotlin en el minuto 18:55 de "Presentamos 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
}
Fragmento de Kotlin en el minuto 19:49 de "Presentamos 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)
}
Fragmento de Kotlin en el minuto 29:31 de "Quick Share overview"
// 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)
Fragmento de Kotlin en el minuto 29:57 de "Quick Share overview"
// 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)
Fragmento de Kotlin en el minuto 30:23 de "Quick Share overview"
// 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)
Fragmento de Kotlin en el minuto 30:49 de "Quick Share overview"
// 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")
    }
  }
}