Compatibilidad con la continuidad multidispositivo en Googlebook

Los usuarios suelen comenzar tareas en su teléfono Android, como revisar un documento, clasificar mensajes o explorar una variedad de elementos, y luego quieren pasar a un Googlebook con su pantalla más grande, teclado físico y espacio de trabajo Multiventana.

La función Continuar en de Android 17 (nivel de API 37) y versiones posteriores permite que la barra de tareas de Googlebook muestre un ícono de la app (llamado sugerencia) para la actividad que un usuario estaba ejecutando en su teléfono cercano. Con un solo clic, los usuarios pueden continuar su actividad en su Googlebook.

La implementación de la transferencia entre dispositivos permite que tu app transfiera el contexto de la actividad para que el usuario reanude su flujo de trabajo, ya sea en tu app adaptable para Android o en el navegador para computadoras, en el lugar exacto en el que lo dejó en su teléfono.

Elige un flujo de transferencia

Antes de habilitar la transferencia multidispositivo, decide cómo se debe abrir cada actividad en la app para teléfonos cuando un usuario selecciona la sugerencia Continuar en el Googlebook:

Flujo de transferencia Segmentación en Googlebook Ideal para API de Key
Transferencia de app a app App nativa para Android que se ejecuta en una ventana de escritorio Apps optimizadas para pantallas grandes y modo de ventanas de escritorio en Google Play HandoffActivityData.Builder
Transferencia de app a app con resguardo web App nativa para Android si está instalada; de lo contrario, una URL en el navegador de escritorio predeterminado Apps optimizadas para una experiencia nativa de ventanas de escritorio, pero que requieren un navegador de respaldo confiable cuando la app no está instalada en el Googlebook setFallbackUri
Transferencia directa a la Web URL en el navegador para computadoras predeterminado (o interceptada por la aplicación nativa si está configurada para Android App Links) Flujos de trabajo en los que tu aplicación web es la experiencia principal para computadoras HandoffActivityData.createWebHandoff

Cómo habilitar la transferencia para una actividad

La función Continuar en está inhabilitada de forma predeterminada y se configura por actividad. Sigue estos pasos en cada actividad de envío del teléfono:

  1. Llama a setHandoffEnabled y pasa true una vez que la actividad haya cargado el estado requerido para la transferencia. Una vez habilitado, el sistema puede solicitar datos de transferencia mientras la actividad está en primer plano o se mueve a segundo plano.
  2. Anula onHandoffActivityDataRequested para devolver un objeto HandoffActivityData no nulo. Puedes consultar isHandoffEnabled en cualquier momento para verificar si la transferencia está activa para la actividad.
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 de la actividad del paquete para la transferencia

El sistema llama a onHandoffActivityDataRequested en dos situaciones:

  • Transferencia en primer plano: La actividad está en primer plano en el teléfono y el usuario inicia una transferencia desde la barra de tareas del Googlebook.
  • Captura del estado en segundo plano: La actividad pasa a segundo plano en el teléfono, y el sistema almacena en caché su carga útil de transferencia en caso de que el usuario seleccione la sugerencia en Googlebook más adelante.

Verifica si la solicitud de transferencia está activa

Inspecciona HandoffActivityDataRequestInfo con isActiveRequest para distinguir entre una transferencia activa iniciada por el usuario y un guardado en segundo plano:

  • Si isActiveRequest devuelve true, el usuario solicitó activamente continuar en otro dispositivo mientras la actividad está visible.
  • Si isActiveRequest devuelve false, el sistema almacena en caché el estado a medida que se detiene la actividad.

Si muestras un indicador visual en el teléfono que confirma que la tarea se está trasladando a la Googlebook, programa la actualización de la IU de forma asíncrona para que onHandoffActivityDataRequested se muestre sin demora.

Cómo adjuntar elementos adicionales de estado y una alternativa web

Una carga útil de HandoffActivityData contiene la siguiente información:

  • Componente de destino (obligatorio para la transferencia de app a app): Pasa this.getComponentName() (el ComponentName de la actividad) o un ComponentName(this, TargetActivity::class.java) explícito a HandoffActivityData.Builder para designar qué actividad se inicia en Googlebook.
  • Extras (opcional): Pasa un PersistableBundle a setExtras() que contenga identificadores y coordenadas de la IU (por ejemplo, IDs de documentos abiertos, claves de pestañas activas o posiciones de desplazamiento). El paquete debe tener un tamaño inferior a 50 KB.
  • URI de resguardo (opcional): Pasa un Uri web a setFallbackUri para que el Googlebook pueda abrir la tarea en el navegador predeterminado si no está instalada tu app para Android.
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"
    }
}

Configura la transferencia directa a la Web

Si tu experiencia principal en el escritorio de Googlebook es una aplicación web, pasa HandoffActivityParams con setAllowHandoffWithoutPackageInstalled(true) a setHandoffEnabled y llama a HandoffActivityData.createWebHandoff en lugar 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)
    }
}

Recibe y restablece el estado en Googlebook

Cuando el usuario selecciona la sugerencia Continuar en la barra de tareas de Googlebook, el sistema inicia el ComponentName de destino en una ventana de escritorio y adjunta los elementos adicionales de PersistableBundle al Intent de inicio.

En Googlebook, controla la transferencia entrante teniendo en cuenta el modo de ventanas de escritorio y los diseños de varios paneles:

  1. Extrae los extras de transferencia: En los métodos onCreate y onNewIntent de la actividad receptora, recupera los extras del Intent entrante. El control de onNewIntent garantiza que, si tu actividad usa el modo de lanzamiento singleTop o se enruta a una ventana de escritorio existente, la ventana se actualice al elemento transferido.
  2. Adapta el estado a diseños de varios paneles: Un estado que ocupaba una vista de detalles en pantalla completa en un teléfono a menudo se asigna a un elemento seleccionado dentro de un diseño de lista-detalles o de panel de asistencia en Googlebook. Completa la selección de la lista principal y el panel de detalles para que la ventana de escritorio tenga un contexto completo.
  3. Preserva el estado restablecido en el cambio de tamaño de la ventana: Almacena el estado transferido en un ViewModel (SavedStateHandle) o rememberSaveable en Jetpack Compose para que el cambio de tamaño de la ventana de formato libre en Googlebook no restablezca la sesión restablecida.
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"
    }
}

Probar la transferencia entre un teléfono y un Googlebook

Para verificar tu implementación de extremo a extremo, haz lo siguiente:

  1. Prepara ambos dispositivos
    • Accede a la misma Cuenta de Google en tu teléfono Android (con Android 17 o una versión posterior) y en tu Googlebook (o en un dispositivo de prueba que se ejecute en modo de ventanas de escritorio).
    • Activa el Bluetooth y conecta ambos dispositivos a la misma red Wi-Fi.
    • En ambos dispositivos, abre Configuración > Dispositivos conectados > Preferencias de conexión > Servicios multidispositivo > Continuar actividad, sigue las indicaciones de configuración y habilita Tareas. Para conocer todos los requisitos previos del entorno, consulta Configuración y prueba.
  2. Instala tu app
    • Para la transferencia de app a app, instala tu compilación en el teléfono y en la Googlebook.
    • Para la reserva web o la transferencia directa a la Web, instala tu compilación en el teléfono y deja la app desinstalada en la Googlebook para que puedas verificar que el navegador abra la URL esperada.
  3. Activa la transferencia.
    • En el teléfono, abre tu app y navega a la actividad que habilita la transferencia (llama a setHandoffEnabled(true, null) para la transferencia de app a app o pasa HandoffActivityParams con setAllowHandoffWithoutPackageInstalled(true) cuando realices pruebas con la app desinstalada en el Googlebook).
    • En el Googlebook, busca el ícono de tu app en el lado derecho de la barra de tareas, que tendrá la insignia de Continuar en, y selecciónalo.
  4. Verifica la restauración en computadoras
    • Confirma que tu app se abra en una ventana de escritorio (o pestaña del navegador) en el elemento exacto y la posición de desplazamiento del teléfono.
    • Cambia el tamaño de la ventana de la app en Googlebook y verifica que el contenido y la selección restablecidos persistan en las transiciones de clase de tamaño de ventana.

Recursos adicionales