Suporte à continuidade entre dispositivos no Googlebook

Os usuários costumam começar tarefas no smartphone Android, como revisar um documento, triar mensagens ou navegar por uma variedade de itens, e depois querem passar para um Googlebook com tela maior, teclado físico e espaço de trabalho com várias janelas.

O recurso Continuar em no Android 17 (nível 37 da API) e versões mais recentes permite que a barra de tarefas do Googlebook mostre um ícone de app (chamado de sugestão) para a atividade que um usuário estava executando no smartphone por perto. Com um único clique, os usuários podem continuar a atividade no Googlebook.

A implementação da transferência entre dispositivos permite que seu app transfira o contexto da atividade para que o usuário retome o fluxo de trabalho, seja no app Android adaptável ou no navegador para computador, exatamente de onde parou no smartphone.

Escolher um fluxo de transferência

Antes de ativar a transferência entre dispositivos, decida como cada atividade no app Telefone deve ser aberta quando um usuário selecionar a sugestão "Continuar no" no Googlebook:

Fluxo de transferência Segmentar no Googlebook Ideal para API Key
Transferência de app para app App Android nativo sendo executado em uma janela de computador Apps otimizados para telas grandes e modo janela para computador no Google Play HandoffActivityData.Builder
Transferência de app para app com fallback da Web App Android nativo, se instalado. Caso contrário, uma URL no navegador padrão para computador Apps otimizados para uma experiência nativa de janela de computador, mas que exigem um navegador confiável quando o app não está instalado no Googlebook. setFallbackUri
Transferência direta para a Web URL no navegador padrão para computador ou interceptado pelo app nativo, se configurado para links de apps Android Fluxos de trabalho em que seu aplicativo da Web é a principal experiência de computador HandoffActivityData.createWebHandoff

Ativar a transferência de uma atividade

O recurso "Continuar em" fica desativado por padrão e é configurado por atividade. Siga estas etapas em cada atividade de envio no smartphone:

  1. Chame setHandoffEnabled e transmita true assim que a atividade carregar o estado necessário para a transferência. Depois de ativado, o sistema pode solicitar dados de transferência enquanto a atividade está em primeiro plano ou sendo movida para segundo plano.
  2. Substitua onHandoffActivityDataRequested para retornar um objeto HandoffActivityData não nulo. Você pode consultar isHandoffEnabled a qualquer momento para verificar se a transferência está ativa para a atividade.
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.os.Bundle
import android.os.PersistableBundle

class DocumentEditorActivity : Activity() {

    private var documentId: String = "doc_42"
    private var scrollOffsetPx: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Enable handoff once the activity is ready to transfer state.
        setHandoffEnabled(true, null)
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val extras = PersistableBundle().apply {
            putString(EXTRA_DOCUMENT_ID, documentId)
            putInt(EXTRA_SCROLL_OFFSET, scrollOffsetPx)
        }

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .build()
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

Estado da atividade do pacote para transferência

O sistema chama onHandoffActivityDataRequested em dois cenários:

  • Transferência em primeiro plano:a atividade está em primeiro plano no smartphone, e o usuário inicia uma transferência na barra de tarefas do Googlebook.
  • Captura do estado em segundo plano:a atividade passa para segundo plano no smartphone, e o sistema armazena em cache a carga de transferência caso o usuário selecione a sugestão no Googlebook mais tarde.

Verificar se a solicitação de transferência está ativa

Inspecione HandoffActivityDataRequestInfo usando isActiveRequest para distinguir entre uma transferência ativa iniciada pelo usuário e um salvamento em segundo plano:

  • Se isActiveRequest retornar true, o usuário terá solicitado ativamente continuar em outro dispositivo enquanto a atividade estiver visível.
  • Se isActiveRequest retornar false, o sistema vai armazenar em cache o estado quando a atividade for interrompida.

Se você mostrar um indicador visual no smartphone confirmando que a tarefa está sendo movida para o Googlebook, programe a atualização da interface de forma assíncrona para que onHandoffActivityDataRequested retorne sem atraso.

Anexar extras de estado e um substituto da Web

Um payload HandoffActivityData contém as seguintes informações:

  • Componente de destino (obrigatório para transferência de app para app): transmita this.getComponentName() (o ComponentName da atividade) ou um ComponentName(this, TargetActivity::class.java) explícito para HandoffActivityData.Builder para designar qual atividade será iniciada no Googlebook.
  • Extras (opcional): transmita um PersistableBundle para setExtras() contendo identificadores e coordenadas da interface (por exemplo, IDs de documentos abertos, chaves de guias ativas ou posições de rolagem). O pacote precisa ter menos de 50 KB.
  • URI de substituição (opcional): transmita um Uri da Web para setFallbackUri para que o Googlebook possa abrir a tarefa no navegador padrão se o app Android não estiver instalado.
import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle
import android.os.PersistableBundle

class ArticleReaderActivity : Activity() {

    private var articleId: String = "android-desktop-windowing"
    private var readingProgressPercent: Int = 45

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        if (handoffRequestInfo.isActiveRequest()) {
            window.decorView.post {
                onHandoffStartedToSecondaryDevice()
            }
        }

        val extras = PersistableBundle().apply {
            putString(EXTRA_ARTICLE_ID, articleId)
            putInt(EXTRA_READING_PROGRESS, readingProgressPercent)
        }

        val fallbackWebUri = Uri.Builder()
            .scheme("https")
            .authority("example.com")
            .appendPath("articles")
            .appendPath(articleId)
            .appendQueryParameter("progress", readingProgressPercent.toString())
            .build()

        return HandoffActivityData.Builder(getComponentName())
            .setExtras(extras)
            .setFallbackUri(fallbackWebUri)
            .build()
    }

    private fun onHandoffStartedToSecondaryDevice() {
        // Save local drafts and update phone UI without blocking handoff.
    }

    companion object {
        const val EXTRA_ARTICLE_ID = "extra_article_id"
        const val EXTRA_READING_PROGRESS = "extra_reading_progress"
    }
}

Configurar a transferência direta para a Web

Se a sua experiência principal de computador no Googlebook for um aplicativo da Web, transmita HandoffActivityParams com setAllowHandoffWithoutPackageInstalled(true) para setHandoffEnabled e chame HandoffActivityData.createWebHandoff em vez de usar HandoffActivityData.Builder:

import android.app.Activity
import android.app.HandoffActivityData
import android.app.HandoffActivityDataRequestInfo
import android.app.HandoffActivityParams
import android.net.Uri
import android.os.Bundle

class WebDashboardActivity : Activity() {

    private var projectId: String = "proj_901"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHandoffEnabled(
            true,
            HandoffActivityParams.Builder()
                .setAllowHandoffWithoutPackageInstalled(true)
                .build()
        )
    }

    override fun onHandoffActivityDataRequested(
        handoffRequestInfo: HandoffActivityDataRequestInfo
    ): HandoffActivityData {
        val webUri = Uri.Builder()
            .scheme("https")
            .authority("app.example.com")
            .appendPath("projects")
            .appendPath(projectId)
            .build()

        return HandoffActivityData.createWebHandoff(webUri)
    }
}

Receber e restaurar o estado no Googlebook

Quando o usuário seleciona a sugestão "Continuar em" na barra de tarefas do Googlebook, o sistema inicia o ComponentName de destino em uma janela de computador e anexa os extras PersistableBundle ao Intent de inicialização.

No Googlebook, processe a transferência de controle com o Modo Janela para Computador e layouts de vários painéis:

  1. Extrair extras de transferência:nos métodos onCreate e onNewIntent da atividade de recebimento, recupere os extras da Intent recebida. O processamento de onNewIntent garante que, se a atividade usar o modo de inicialização singleTop ou rotear para uma janela de computador já aberta, a janela será atualizada para o item transferido.
  2. Adaptar o estado a layouts de vários painéis:um estado que ocupava uma visualização de detalhes em tela cheia em um smartphone geralmente é mapeado para um item selecionado em um layout de detalhes e listas ou de painel de suporte no Googlebook. Preencha a seleção da lista principal e o painel de detalhes para que a janela da área de trabalho tenha contexto completo.
  3. Preservar o estado restaurado ao redimensionar a janela:armazene o estado transferido em um ViewModel (SavedStateHandle) ou rememberSaveable no Jetpack Compose para que o redimensionamento livre da janela no Googlebook não redefina a sessão restaurada.
import android.app.Activity
import android.content.Intent
import android.os.Bundle

class DocumentEditorReceiverActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Restore from the handoff intent only on initial launch so subsequent
        // desktop window configuration changes use saved instance state.
        if (savedInstanceState == null) {
            handleHandoffIntent(intent)
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        handleHandoffIntent(intent)
    }

    private fun handleHandoffIntent(incomingIntent: Intent?) {
        val documentId = incomingIntent?.getStringExtra(EXTRA_DOCUMENT_ID)
            ?: return
        val scrollOffsetPx = incomingIntent.getIntExtra(EXTRA_SCROLL_OFFSET, 0)

        openDocumentInDesktopWorkspace(
            documentId = documentId,
            initialScrollOffsetPx = scrollOffsetPx
        )
    }

    private fun openDocumentInDesktopWorkspace(
        documentId: String,
        initialScrollOffsetPx: Int
    ) {
        // Load the document into the list-detail layout and restore scroll position.
    }

    companion object {
        const val EXTRA_DOCUMENT_ID = "extra_document_id"
        const val EXTRA_SCROLL_OFFSET = "extra_scroll_offset"
    }
}

Testar a transferência entre um smartphone e um Googlebook

Para verificar a implementação de ponta a ponta:

  1. Prepare os dois dispositivos
    • Faça login na mesma Conta do Google no smartphone Android (com Android 17 ou mais recente) e no Googlebook (ou em um dispositivo de teste no modo janela para computador).
    • Ative o Bluetooth e conecte os dois dispositivos à mesma rede Wi-Fi.
    • Nos dois dispositivos, abra Configurações > Dispositivos conectados > Preferências de conexão > Serviços multidispositivo > Continuar atividade, siga as instruções de configuração e ative Tarefas. Para conferir todos os pré-requisitos do ambiente, consulte Configuração e teste.
  2. Instalar o app
    • Para o transferência de app para app, instale o build no smartphone e no Googlebook.
    • Para fallback da Web ou transferência direta para a Web, instale o build no smartphone e deixe o app desinstalado no Googlebook para verificar se o navegador abre o URL esperado.
  3. Acionar a transferência
    • No smartphone, abra o app e navegue até a atividade que permite a transferência (chamando setHandoffEnabled(true, null) para transferência de app para app, ou transmitindo HandoffActivityParams com setAllowHandoffWithoutPackageInstalled(true) ao testar com o app desinstalado no Googlebook).
    • No Googlebook, confira o lado direito da barra de tarefas para encontrar o ícone do app com o selo "Continuar em" e selecione-o.
  4. Verificar a restauração do computador
    • Confirme se o app abre em uma janela de computador (ou guia do navegador) no item exato e na posição de rolagem do smartphone.
    • Redimensione a janela do app no Google Livros e verifique se o conteúdo e a seleção restaurados persistem nas transições de classe de tamanho de janela.

Outros recursos