Apps foundation

Interações multidispositivo simplificadas

Alto-falantes

Sobre

Sobre a palestra

Como os usuários alternam entre smartphones, wearables e periféricos de áudio, oferecer experiências fluidas em vários dispositivos se tornou essencial para o engajamento. No entanto, as abordagens de rede padrão geralmente resultam em consumo de bateria, picos de latência e sessões de usuário fragmentadas.

Esta investigação apresenta APIs especializadas do Android criadas para comunicação multidispositivo, oferecendo padrões eficientes de memória e energia para manter as transições entre dispositivos instantâneas, confiáveis e intuitivas.

Resumo

  • O Gerenciador de Dispositivos Complementar substitui os serviços em primeiro plano para periféricos Bluetooth, ativando apps com base na presença e gerenciando conexões sem consumir muita bateria. O Android 17 adiciona novos perfis especificamente para dispositivos médicos e trackers de condicionamento físico.
  • A Jetpack Telecom atua como um coordenador de chamadas do sistema para apps de VoIP em dispositivos conectados, como relógios e carros, funcionando até o Android 8. Ele também sincroniza as ações de silenciar e desligar usando o CallControlScope.
  • Os próximos recursos do Jetpack Telecom 1.1.0 incluem históricos de ligações unificados no discador do sistema (SDK 36.1+) e fatiamento de rede 5G automático em redes de operadoras compatíveis.
  • O SDK do Quick Share será lançado no ano que vem para que os desenvolvedores possam incorporar o compartilhamento de arquivos local com criptografia de ponta a ponta diretamente em apps Android e iOS. O recurso Aproximar para Compartilhar (lançado com o Pixel 11) pode até mesmo ativar automaticamente um app de recebimento fechado.

Recursos

Gemini Notebook

Tópicos

  • entre dispositivos
  • companion-device-manager
  • jetpack-telecom
  • quick-share
  • bluetooth
  • voip
  • ponto a ponto

Transcrição

Introdução às interações entre dispositivos

  • Olá!

  • Este é o curso "Como permitir interações perfeitas entre dispositivos".

  • Meu nome é Hanrui, e hoje estou com meus colegas, Yacine e Nataraj.

  • Quando falamos de conectividade, o que costuma vir à mente é um link de rede básico ou fones de ouvido Bluetooth.

  • No Android, a conectividade é muito mais ampla do que isso.

  • O objetivo é fazer com que os dispositivos funcionem melhor em conjunto.

  • Hoje, vamos analisar cenários comuns de vários dispositivos e mostrar qual API é adequada para cada um deles.

  • Por muito tempo, criamos apps para um único dispositivo, apenas smartphones.

  • Depois, os wearables decolaram.

  • Logo, tornou-se muito comum ter um smartwatch pareado com um smartphone, sincronizando dados em segundo plano ou recebendo chamadas diretamente.

  • Adicionar fones de ouvido sem fio.

  • Agora, os usuários podem trocar de dispositivo de áudio durante uma chamada, do relógio para o smartphone, para os fones de ouvido ou para o carro.

  • Mais recentemente, muitos usuários também carregam um segundo dispositivo de tela grande, como um dobrável ou tablet.

  • Eles esperam que o app funcione sem problemas em todos esses dispositivos.

  • Isso se estende a laptops e Chromebooks, em que os usuários podem executar apps Android e ficar conectados diretamente da mesa de trabalho.

  • Em 2023, um usuário médio já tinha entre três e quatro dispositivos.

  • Os usuários esperam mais do que apenas uma funcionalidade independente nesses dispositivos.

  • Eles buscam uma experiência extraordinária de integração.

  • Essa experiência de integração fica ainda mais evidente quando as pessoas estão no mesmo ambiente, como nós hoje, e querem uma maneira fácil de compartilhar fotos, links ou apps com quem está por perto.

  • Analisando os números, o ecossistema é enorme e está crescendo rapidamente.

  • Neste ano, há cerca de 22 bilhões de dispositivos de IoT no mundo todo, um crescimento de 14% ano a ano.

  • Entre todos esses endpoints, o Wi-Fi representa um terço e o Bluetooth, um quarto.

  • A conectividade multidispositivo não é mais um caso extremo.

  • Ela faz parte do nosso dia a dia.

  • No entanto, hoje em dia, a maioria dos apps ainda cria recursos multidispositivo em APIs de rede e Bluetooth de baixo nível, o que gera três desafios.

  • Primeiro, bateria e memória.

  • Manter verificações e serviços em segundo plano em execução consome bateria e memória.

  • Segundo, latência.

  • Atrasos imprevisíveis interrompem chamadas em tempo real e a sincronização de dados.

  • E, em terceiro lugar, o atrito de conexão.

  • Gerenciar a descoberta, o pareamento e a reconexão em todos os dispositivos é complexo e pode facilmente prejudicar a experiência do usuário.

  • Para acabar com essas dores de cabeça, criamos APIs específicas em três áreas principais para simplificar os recursos multidispositivo.

  • Vou começar com os apps complementares, depois o Nataraj vai falar sobre os apps de VoIP e, por fim, o Yacine vai falar sobre o compartilhamento P2P.

Visão geral dos apps complementares

  • Vamos analisar os apps complementares primeiro.

  • Um app complementar é um aplicativo que você instala no smartphone para que um dispositivo complementar, como um smartwatch ou um sensor de glicose, funcione bem com ele.

  • Nesta seção, vamos focar nos dispositivos conectados por Bluetooth.

  • Normalmente, há quatro categorias de dispositivos complementares.

  • Primeiro, dispositivos médicos, como sensores de glicose e monitores de sinais vitais.

  • E wearables, como smartwatches e pulseiras fitness.

  • Fones de ouvido, com Pareamento Rápido e controles inteligentes.

  • E kits para carros, para itens como chave digital do carro e chamadas viva-voz.

  • Cada categoria tem as próprias jornadas do usuário, mas todas dependem de uma conexão confiável entre o dispositivo e o smartphone.

  • Para dispositivos médicos, essa conexão pode até ser vital.

  • Vamos analisar quatro necessidades comuns que identificamos nessas categorias.

  • Primeiro, o gerenciamento de dispositivos.

  • O app precisa encontrar o dispositivo certo entre todos os que estão por perto, como o relógio aqui, não o fone de ouvido ou o alto-falante, e iniciar o fluxo de pareamento para se conectar.

  • Em segundo lugar, a verificação de presença.

  • O app precisa saber quando o dispositivo está por perto e quando não está.

  • E, quando o dispositivo aparece, o app precisa de uma maneira de ser ativado para que possa fazer o trabalho dele, mesmo que o usuário não o tenha aberto.

  • Terceiro, manutenção da conexão.

  • Depois de conectados, os apps querem que a conexão seja confiável e persistente.

  • Se a conexão cair, por exemplo, se o usuário sair do alcance e voltar, o app precisará se reconectar automaticamente, mesmo que não esteja em execução no momento.

  • E, em quarto lugar, a sincronização e transferência de dados.

  • É aí que está o valor real.

  • Sincronizar leituras de sensores, enviar atualizações de firmware ou enviar configurações para o dispositivo.

  • Para dispositivos médicos, isso pode ser fundamental.

  • Uma leitura perdida pode significar um alerta perdido.

  • Todos eles precisam que o processo do app esteja ativo e conectado no momento certo.

  • Como os apps atendem a todas essas necessidades?

  • Tradicionalmente, os apps dependiam muito do serviço em primeiro plano.

  • Veja uma implementação típica.

  • Se você já criou um app complementar, isso pode parecer familiar.

  • Você declara as permissões de busca por Bluetooth e conexão, as permissões de serviço em primeiro plano e o tipo de serviço em primeiro plano do dispositivo conectado. Depois, você declara a permissão de notificações, porque um serviço em primeiro plano precisa mostrar uma notificação 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" />
  • Em seguida, execute um serviço fixo para manter o processo do app ativo.

    class BleForegroundService : Service() {
       override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
          …
          return START_STICKY
       }
    }
  • Isso funciona, mas consome bateria e memória porque o serviço é executado o tempo todo, mesmo quando o dispositivo não está por perto.

  • Por isso, criamos o Gerenciador de dispositivos complementar, ou CDM, na sigla em inglês.

  • Primeiro, ela pode ativar o app com base na presença do dispositivo e, enquanto o dispositivo estiver dentro do alcance, o sistema vai manter o processo do app ativo.

  • Não é necessário um serviço em primeiro plano.

  • Em segundo lugar, o CDM é compatível com perfis de dispositivo.

  • Com um perfil de dispositivo, o usuário concede todas as permissões necessárias para o app com um único toque, em vez de uma série de caixas de diálogo de permissão.

  • Isso melhora significativamente a experiência do usuário e a taxa de conversão.

  • O perfil de relógio está disponível desde o Android 12, e o de óculos, desde o Android 14.

  • No Android 17, adicionamos dois novos perfis específicos para dispositivos médicos e trackers de condicionamento físico.

  • Também há alguns perfis privilegiados apenas para apps do sistema.

  • Se houver uma necessidade mais ampla do ecossistema, podemos considerar a abertura para mais desenvolvedores.

Implementação usando o Gerenciador de dispositivos complementar

  • É muito fácil usar o CDM.

  • Vamos usar um dispositivo médico como exemplo.

  • Primeiro, declare as permissões necessárias: uma para o dispositivo médico, uma para execução em segundo plano e outra para observar a presença do 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" />
  • Ao contrário das permissões que acabamos de ver com a implementação do serviço em primeiro plano, todas as permissões listadas aqui são normais, ou seja, serão concedidas na instalação, sem caixa de diálogo.

  • A única coisa que o usuário precisa aprovar é a associação.

  • Vamos ver como criar essa associação.

  • Aqui, criamos uma solicitação de associação com um filtro de dispositivo para que apenas o dispositivo certo apareça.

    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 como verdadeiro, porque queremos apenas uma correspondência, e defina o perfil como médico.

  • Em seguida, chame associate.

  • O CDM processa a verificação.

  • Em onAssociationPending, vamos receber um IntentSender.

  • Inicie-o, e o sistema vai mostrar uma caixa de diálogo de associação.

  • Com um perfil médico, esse toque não apenas aprova a associação, mas também concede todas as permissões necessárias, até mesmo ignorando o modo Não Perturbe, o que é essencial para alertas críticos.

  • Depois que o usuário tocar em "Permitir", vamos receber em onAssociationCreated com AssociationInfo, que inclui um ID de associação.

  • Em seguida, podemos observar a presença do dispositivo chamando startObservingDevicePresence com esse ID de associação.

  • Também é necessário implementar um serviço que 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 o dispositivo entra no alcance, o sistema se vincula ao serviço, mesmo que o app não esteja em execução.

  • O sistema vai chamar onDevicePresenceEvent com EVENT_BLE_APPEARED.

  • Depois, você pode conectar e sincronizar.

  • Quando o dispositivo sair, você vai receber EVENT_BLE_DISAPPEARED.

  • Em seguida, faça a limpeza.

  • Não há serviço sempre ativo nem notificações apenas para manter o app ativo.

  • Agora, precisamos admitir que o CDM tem algumas limitações que os desenvolvedores precisam considerar.

  • Primeiro, as versões do SO.

  • A maioria dos recursos do CDM foi lançada no Android 12 e 13.

  • Mas algumas melhorias legais chegaram no Android 16 e os novos perfis no Android 17.

  • Portanto, verifique a versão do SO durante a execução e mantenha um substituto para versões mais antigas.

  • Dito isso, o SDK de destino aumenta a cada ano, e o SDK mínimo tende a acompanhar.

  • Para lidar com a pressão de memória que os apps estão enfrentando agora e vão enfrentar no futuro, recomendamos que eles migrem do serviço em primeiro plano para o CDM o mais rápido possível.

  • Se você tiver um caso de uso que não seja compatível com o CDM, entre em contato.

  • Em segundo lugar, hoje o CDM é compatível com a verificação em segundo plano, mas não com o anúncio em segundo plano.

  • Raramente ouvimos falar de uma necessidade como essa.

  • No entanto, se o app precisar que o smartphone funcione como um periférico e anuncie continuamente em segundo plano, gostaríamos de saber mais sobre isso.

  • Terceiro, o CDM usa o modo de verificação de baixa energia.

  • Ele troca um pouco da capacidade de resposta em tempo real por bateria e memória.

  • Portanto, não espere ativações instantâneas.

  • Por fim, não há visualização de payload.

  • Não é possível se registrar para uma característica ou dados de anúncio específicos.

  • Mas, quando o dispositivo estiver dentro do alcance, o app será ativado.

  • Assim, ele pode se conectar e verificar o payload conforme necessário.

  • Isso é tudo sobre apps complementares e CDM.

  • Vou passar a palavra para Nataraj, que vai falar sobre apps de VoIP.

Desafios com chamadas VoIP entre dispositivos

  • O CompanionDeviceManager é a ferramenta certa se o app tiver um periférico dedicado e se comunicar continuamente por ele.

  • Olá!

  • Sou Nataraj K R.

  • Agora, vou falar sobre o próximo cenário multidispositivo, que é a chamada em tempo real.

  • Hoje em dia, a forma como as pessoas lidam com as ligações está mudando.

  • Mais de 40% das chamadas estão acontecendo em redes VoIP.

  • E o Android tem um papel importante nisso.

  • Na verdade, 71% dessas ligações acontecem em dispositivos Android, o que é quase todas as chamadas.

  • Não são apenas as ligações.

  • O ecossistema de dispositivos que as pessoas têm também está crescendo muito.

  • Há mais de 250 milhões de carros com o Android Auto nas ruas, e também estamos enviando mais de um bilhão de dispositivos Bluetooth para os usuários.

  • Fones de ouvido, relógios Wear OS, carros com Android Auto e alto-falantes Bluetooth são todos os dispositivos que os usuários têm ao redor do smartphone, e eles querem que as chamadas sejam transferidas entre todos esses dispositivos sem problemas.

  • Por exemplo, o usuário recebe uma ligação e olha para o relógio para saber quem está ligando.

  • Ela atenderia a ligação no relógio.

  • Eles ativam ou desativam o som conforme necessário, colocam os fones de ouvido e continuam a chamada.

  • Se quiserem sair, eles entram no carro e esperam que a chamada mude de dispositivo para o veículo sem interrupções.

  • Isso se tornou uma rotina normal para o usuário, e não é uma experiência premium.

  • Para o usuário, esse é um requisito básico, e cabe a nós, desenvolvedores Android, oferecer essa experiência integrada nos nossos apps de VoIP.

  • Mas se você tentar criar tudo isso do zero, vai acabar com três grandes problemas.

  • Primeiro, a fragmentação com soquetes Bluetooth.

  • Como o sistema não sabe que seu app está processando uma chamada, você precisaria criar soquetes Bluetooth e sessões de mídia personalizados para que a chamada seja projetada no relógio ou no Android Auto.

  • O segundo problema é o foco de áudio.

  • Como você precisa lidar com APIs AudioManager e Bluetooth de baixo nível, ocorrem vazamentos de áudio, e isso não é uma prioridade para o sistema, certo?

  • Seu app está em uma chamada VoIP. Quando uma chamada de operadora é recebida, ela interrompe a chamada VoIP, e o modem assume o áudio, deixando o app sem som.

  • Pior ainda, quando a chamada celular terminar, o foco de áudio não será devolvido ao app.

  • E o terceiro problema é a sincronização.

  • O usuário tocava em "Desativar som" no relógio, mas a interface do smartphone ainda mostrava a chamada com o som ativado.

  • Isso cria uma grande confusão.

  • O usuário desconectava a chamada na coluna de direção do carro, mas o smartphone ainda mostrava uma notificação de chamada em andamento.

  • É uma grande frustração para o usuário quando isso acontece.

Introdução ao Jetpack Telecom

  • Para resolver todos esses problemas, criamos o Jetpack Telecom.

  • Portanto, você registra sua chamada uma vez com o Jetpack Telecom usando o CallsManager, e o Jetpack Telecom atua como um coordenador central de chamadas do sistema.

  • Ele é responsável por projetar sua chamada no relógio, no Android Auto e em outras plataformas remotas que o usuário tenha.

  • Ele também cuida do gerenciamento da seleção de áudio.

  • E o mais importante: tudo isso funciona perfeitamente, até mesmo no Android 8.

  • Projetar a chamada na tela do relógio ou do carro é só metade do processo, porque o usuário quer controlar o estado da chamada nesses dispositivos.

  • Eles querem ativar ou desativar o som da chamada no relógio, conforme necessário.

  • Eles querem desligar a chamada usando o volante do carro ou tocando duas vezes nos fones de ouvido.

  • Assim, a Jetpack Telecom detecta esses eventos de hardware e os apresenta como callbacks em um só lugar, que é CallControlScope.

  • Basta ouvir esses callbacks, processar o mecanismo de chamada e atualizar o estado da chamada de acordo.

  • E o Jetpack Telecom assume a responsabilidade de sincronizar esse estado de chamada entre os dispositivos.

  • Por isso, a biblioteca oferece benefícios em duas categorias.

  • Primeiro, obviamente, ele resolve todos os problemas que mencionamos.

  • Assim, em vez de lidar com APIs AudioManager de baixo nível, a biblioteca oferece endpoints de chamada declarativos, gerencia o foco de áudio e sincroniza os dispositivos.

  • Ele é responsável por transferir a chamada entre os dispositivos.

  • E também cuida de quando uma chamada de celular chega.

  • Mas o outro benefício que vem com a biblioteca é que, como ela é criada com base no Kotlin, sempre que você adiciona uma chamada com o Jetpack Telecom, ela abre um escopo de corrotina do Kotlin.

  • Assim, ele aproveita as corrotinas do Kotlin, que permanecem ativas exatamente até o fim da chamada.

  • Portanto, qualquer job executado em relação à chamada é limpo automaticamente assim que ela é desconectada.

  • Assim, você não terá jobs invasores, e o ciclo de vida da chamada também será gerenciado pela biblioteca.

  • Agora, você deve estar pensando que essa biblioteca é incrível e quer implementá-la, certo?

  • É muito simples.

  • A etapa 1 é, obviamente, declarar a dependência.

  • Depois, declare a permissão necessária, que é MANAGE_OWN_CALLS, e instancie um objeto CallsManager.

    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
  • Depois de instanciar o objeto CallsManager, registre o app com os recursos que você quer oferecer, como videochamadas ou streaming de chamadas.

    // Initialize CallsManager & register supported capabilities once
    val callsManager = CallsManager(context)
    
    callsManager.registerAppWithTelecom(
        capabilities = CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING or
                       CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING
    )
  • Depois, a etapa 2.

  • Agora que você registrou seu app no Jetpack Telecom, é hora de processar as chamadas recebidas e ligações efetuadas.

  • Para isso, você precisa criar um objeto CallAttributesCompat, que contém os metadados da chamada, como o displayName do autor ou destinatário da chamada, o endereço, que é o endpoint do autor da chamada, a direção, especificando se a chamada é recebida ou efetuada para que a Jetpack Telecom possa apresentar a respectiva interface nessas superfícies remotas, e, em seguida, callType, se é uma chamada de áudio ou videochamada para que você receba os recursos necessários.

    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
    }
  • Quando o objeto de metadados estiver pronto, use o método callsManager.addCall para adicioná-lo.

  • Isso abre um escopo de corrotina de controle de chamadas.

  • Depois de adicionar a chamada, vamos para a etapa 3.

  • A etapa 3 é detectar as ações que o usuário vai realizar nessas plataformas remotas.

  • Como você tem CallControlScope, basta coletar os fluxos do 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)
    }
  • Colete isMuted para reagir sempre que o usuário desativar o som da chamada nessas plataformas remotas e, assim, atualizar o estado da chamada.

  • Colete currentCallEndpoint para saber em qual endpoint a chamada está acontecendo e atualizar a IU de acordo.

  • Você pode mostrar esse dispositivo específico na interface do smartphone.

  • E sempre que o usuário quiser mudar de um dispositivo para outro, basta chamar o método requestEndpointChange com o endpoint preferido que o usuário quiser.

  • É simples assim.

  • Com essas três etapas, seu app está pronto para usar a Jetpack Telecom integrada.

  • Mas não é só isso.

  • Com o Jetpack Telecom 1.1.0, estamos trazendo muito mais recursos, como históricos de ligações unificados, o que significa que todas as suas chamadas VoIP agora vão aparecer de forma nativa no discador do sistema.

  • Assim, a vantagem é que os usuários poderão instanciar uma chamada de retorno com um toque diretamente do discador do sistema para o app.

  • Assim, não é preciso procurar o app específico entre todos os outros.

  • Eles podem simplesmente retornar a ligação usando o discador do sistema que já estão acostumados a usar.

  • Mas, para alguns, mas se você, mas também estamos fornecendo controles granulares.

  • Se você não quiser que uma chamada específica seja registrada no sistema, basta definir a flag isLogExcluded como verdadeira no objeto CallAttributesCompat.

  • O objeto de metadados que mencionei anteriormente, basta definir como verdadeiro, e sua chamada não será registrada no sistema nem vai aparecer no discador do sistema.

  • O segundo recurso incrível que vai surgir com a versão 1.1.0 é o fracionamento de rede 5G.

  • Nas redes de operadoras compatíveis, o que acontece é que as redes oferecem fatias de rede premium. Isso significa que, para um caso de uso específico, a operadora oferece uma fatia de rede com baixa latência e sem outras interrupções, para que você tenha uma melhor qualidade e experiência.

  • Portanto, nas redes de operadoras compatíveis, você terá esse recurso pronto para uso, sem precisar de nenhum esforço extra, nem mesmo uma única linha de código.

  • Depois de integrar a biblioteca, suas chamadas vão aproveitar essas fatias de rede, e você terá uma latência menor e uma performance melhor.

  • Mas, se por algum motivo você não quiser esse recurso, use a flag CAPABILITY_OPT_OUT_OF_PREMIUM_NETWORK para desativá-lo.

  • O Jetpack Telecom já foi implementado pelos principais apps de comunicação globais, como Google Meet, WhatsApp, KakaoTalk e Zoho Arattai.

  • Agora é com você. Agora é com você integrar essa biblioteca ao seu app e oferecer uma experiência melhor e mais integrada para o usuário.

  • Agora que já falamos sobre as chamadas em tempo real, vou passar a palavra para meu colega Yacine, que vai falar sobre o recurso de tocar para compartilhar.

  • Nossa equipe agradece.

Desafios do compartilhamento ponto a ponto

  • Pessoal, sei que vocês estão um pouco cansados, mas não se preocupem, esta é a última seção.

  • Vou falar sobre o compartilhamento de arquivos, mas há outros casos de uso para o compartilhamento peer-to-peer.

  • No ecossistema de dispositivos móveis, mais de 14 bilhões de fotos e vídeos são compartilhados em redes sociais e apps de mensagens todos os dias. É uma quantidade enorme.

  • E todos esses arquivos são compartilhados na maioria das vezes pela nuvem. Você tira a foto, faz o upload dela pelo seu serviço favorito e a pessoa precisa fazer o download da mesma forma do outro lado.

  • E o mesmo acontece quando você tenta convidar alguém ou um amigo para uma rede social, para colaborar em uma playlist ou documento. Geralmente, você precisa digitar o nome de usuário ou o e-mail, o que leva um tempo no teclado.

  • Você pode cometer um erro, e isso não é uma experiência muito boa, principalmente quando vocês estão literalmente sentados um ao lado do outro. É tipo, ah, tenho que enviar pela nuvem, tenho que esperar.

  • Não é muito bom.

  • Hoje em dia, todos nós temos smartphones, dispositivos, tablets e dobráveis sofisticados, e tiramos fotos incríveis com eles.

  • Mas, quando você compartilha por algumas dessas redes sociais ou apps de mensagens, por problemas de desempenho e armazenamento, elas são compactadas. No final, na sua tela incrível, elas acabam ficando pixeladas ou sem a melhor qualidade.

  • Essa não é a melhor experiência que você gostaria de ter no seu dispositivo premium.

  • Em segundo lugar, os arquivos agora são bem grandes.

  • Uma única foto pode ter facilmente 10 MB, e um vídeo de 5 minutos pode ter cerca de 500 MB. Por isso, o upload e o download de tudo isso levam bastante tempo.

  • Por fim, do outro lado, você precisa esperar essa latência.

  • Às vezes, você pode estar em um ônibus lotado e, enquanto ele se move, a latência não é das melhores, a cobertura de rede é ruim e a transferência de arquivos é interrompida. Às vezes, devido a essa latência muito longa, pode ocorrer um tempo limite e a transferência de arquivos é cancelada, o que é muito frustrante para o usuário.

  • Então, em vez de depender da nuvem para compartilhar todos esses arquivos e ter que esperar por tudo isso, por que não usar uma rede local, como Bluetooth ou Wi-Fi?

  • Mas se você tentar fazer isso por conta própria, obviamente, terá que lidar com o gerenciador de Bluetooth, o gerenciador de Wi-Fi, ter que escrever seu próprio protocolo, ou seja, nem todo mundo quer perder tempo fazendo isso.

  • Mas, na verdade, já existe algo no Android.

  • Ele já está disponível e talvez você já tenha ouvido falar dele: o Quick Share.

Visão geral do Quick Share

  • O Quick Share no Android permite que você tenha essa abstração já implementada no sistema e possa compartilhar arquivos com outro dispositivo sem precisar gerenciar tudo isso.

  • Ele já está disponível na sua página de compartilhamento e alterna entre os protocolos para escolher o melhor para seu dispositivo. Ele está disponível no Marshmallow e versões mais recentes, o que, hoje em dia, é um público enorme, certo?

  • Por fim, essa interface e experiência do usuário que você vê ao lado realmente ajudam seus usuários.

  • Eles já sabem o que é a interface, não precisam aprender a usá-la, já a conhecem.

  • Mas sabemos que os desenvolvedores também querem ter controle sobre a experiência.

  • Você conhece seu app e seus usuários melhor do que nós, provavelmente.

  • Então, por que não ter o Quick Share diretamente no seu app?

  • Então, conversamos com desenvolvedores e vimos que o Quick Share já é bastante usado.

  • Ele está disponível no Android, em Chromebooks, recentemente no Google Livros e também no Windows, ou seja, em mais de 3 bilhões de dispositivos. É enorme.

  • E também trouxemos com o Pixel 11 a capacidade de tocar para compartilhar.

  • Assim, em vez de escolher o dispositivo com que você quer compartilhar, basta tocar e, com o NFC, fazemos essa detecção do dispositivo diretamente para você.

  • Você pode selecionar uma foto ou um arquivo, tocar em dois dispositivos e compartilhar diretamente com um toque.

  • E, novamente, conhecemos desenvolvedores que querem ter controle sobre a experiência.

  • Sabemos que essa é a melhor maneira de os usuários usarem um recurso específico, neste caso, o compartilhamento de arquivos.

  • Então, por que não incorporar o Quick Share ao seu app?

  • Por isso, no ano que vem, vamos disponibilizar o SDK do Quick Share diretamente para você.

  • Você poderá usá-lo diretamente no seu app, e ele já vem com segurança integrada, como criptografia de ponta a ponta entre os dois dispositivos.

  • Como as transferências acontecem entre essas redes locais, você terá a velocidade máxima, obviamente no Wi-Fi da sua casa, mas também entre vocês dois, basta criar um ponto de acesso automaticamente, você não precisa lidar com isso.

  • E para dispositivos compatíveis com o Wi-Fi Direct, ele já está incluído.

  • E os arquivos que você tirou com sua câmera incrível vão manter a melhor resolução e os detalhes, para que você possa vê-los bem na tela grande.

  • E, como ele depende de redes locais, você pode usá-lo no metrô, mesmo na parte mais profunda, e ainda terá rede para compartilhar com seu amigo.

  • No avião, você vai estar a não sei quantos mil metros de altura, no céu, e ainda vai poder compartilhar esses arquivos.

  • Por fim, para você, como desenvolvedor, é uma enorme economia de custos em termos de dados, porque, primeiro, seus usuários não precisam usar o plano de dados, e isso é muito importante quando eles estão viajando como turistas.

  • Além disso, você não precisa manter isso nos seus próprios servidores.

  • No ano passado, mencionamos aos desenvolvedores e usuários que, com o Quick Share, eles poderão compartilhar arquivos com dispositivos iPhone pelo AirDrop.

  • Com o Quick Share, estamos trazendo o mesmo para os desenvolvedores.

  • Vamos ter um SDK do Quick Share para iOS para que os desenvolvedores que têm apps iOS possam compartilhar arquivos entre as duas plataformas, independente do dispositivo.

  • Tudo isso é uma apresentação sofisticada. Agora, vamos dar uma olhada rápida no código.

  • Tentamos simplificar ao máximo.

  • Você inicia um QuickShareClient, anexa os arquivos que quer compartilhar e, ao iniciar a sessão, aparece essa janela modal que você está vendo ao lado, a página inferior em que os usuários podem ver os arquivos que estão sendo enviados como prévias. Se você considerar o conteúdo sensível, também poderá ativar essa opção na tela.

    // 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 você quiser receber um arquivo, novamente, no seu app, é a mesma coisa. Claro, nesse caso, você não está adicionando um anexo e, assim que a sessão é iniciada, não precisa lidar com nada disso.

    // 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)
  • Até agora, você viu literalmente apenas 20 linhas de código em ambos os lados.

  • Sabemos que, com o Quick Share e o aproxime para compartilhar, a capacidade de compartilhar arquivos apenas tocando nos dispositivos também possibilita um novo caso de uso.

  • Seu app pode estar fechado no outro dispositivo e, ao tocar, assim que o arquivo for transferido, ele será aberto automaticamente e você vai receber o URI para fazer algum processamento no dispositivo, por exemplo, ou mostrar algo ao usuário diretamente no 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)
  • Portanto, não é necessário abrir o app no dispositivo receptor.

  • Com o aproxime para compartilhar, ele será aberto automaticamente.

  • E, claro, se você quiser que sua IU reaja a essa transferência de arquivos, basta usar um fluxo do Kotlin para ter todos os eventos necessários para processar isso.

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

Resumo e conclusão

  • Já vimos muita coisa hoje, então vou encerrar.

  • E não se preocupe, você terá alguns minutos para fazer uma pausa.

  • O CompanionDeviceManager é a melhor API para lidar com qualquer interação Bluetooth, inclusive as contínuas, como Hanrui apresentou anteriormente.

  • Se o app tiver chamadas de voz sobre IP, não precisa ser o caso de uso principal. Por exemplo, você pode ter clientes de suporte como parte do app em que o usuário pode ligar para o suporte.

  • Nesse caso, a melhor maneira de lidar com chamadas de voz sobre IP no Android é com o Jetpack Telecom.

  • Por fim, para compartilhar arquivos entre dispositivos ou abrir apps, como mencionei, o Quick Share é a solução.

  • Então, agradeço a todos e, antes de irem, só um aviso rápido: vamos estar lá fora depois para responder às perguntas de vocês.

  • Temos horários de atendimento para tirar dúvidas sobre todos os casos de uso que apresentamos.

  • E não se esqueça de compartilhar o que aprendeu na sua palestra ou em outras sessões, obviamente, nas redes sociais.

  • Temos o #AndroidDevSummit.

  • É muito importante para nós saber que temos engajamento, que as pessoas estão realmente gostando do evento.

  • Isso nos ajuda a saber que temos uma comunidade que queremos ver de novo.

  • E pegue seu pingente social no balcão de informações para adicionar ao chaveiro do bot do Android.

  • Fiz certo?

  • Nossa equipe agradece.

Código

Snippets de código

Snippet XML em 06:17 em "Visão geral dos apps complementares"
<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" />
Snippet do Kotlin em 06:34 em "Visão geral dos apps complementares"
class BleForegroundService : Service() {
   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
      …
      return START_STICKY
   }
}
Snippet XML em 08:03 em "Implementação usando o Gerenciador de dispositivos complementar"
<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" />
Snippet do Kotlin aos 08:38 em "Implementação usando o Gerenciador de dispositivos complementar"
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?) { … }
   }
)
Snippet do Kotlin em 09:47 em "Implementação usando o Gerenciador de dispositivos complementar"
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) { … }
}
Snippet XML em 18:29 em "Introdução ao Jetpack Telecom"
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Snippet do Kotlin aos 18:36 em "Introdução ao 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
)
Snippet do Kotlin aos 18:55 em "Introdução ao 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
}
Snippet do Kotlin aos 19:49 em "Introdução ao 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)
}
Snippet do Kotlin em 29:31 em "Visão geral do Quick Share"
// 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)
Snippet do Kotlin em 29:57 em "Visão geral do Quick Share"
// 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)
Snippet do Kotlin aos 30:23 em "Visão geral do Quick Share"
// 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)
Snippet do Kotlin aos 30:49 em "Visão geral do Quick Share"
// 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")
    }
  }
}