Assurer la continuité inter-appareils sur un Googlebook

Les utilisateurs commencent souvent des tâches sur leur téléphone Android (par exemple, examiner un document, trier des messages ou parcourir un assortiment d'articles), puis souhaitent passer à un Googlebook avec son écran plus grand, son clavier physique et son mode multifenêtre.

La fonctionnalité Continuer sur d'Android 17 (niveau d'API 37) ou version ultérieure permet à la barre des tâches du Googlebook d'afficher une icône d'application (appelée suggestion) pour l'activité qu'un utilisateur exécutait sur son téléphone à proximité. En un seul clic, les utilisateurs peuvent reprendre leur activité sur leur Googlebook.

L'implémentation du transfert inter-appareils permet à votre application de transférer le contexte d'activité afin que l'utilisateur reprenne son workflow, soit dans votre application Android adaptative, soit dans le navigateur pour ordinateur, exactement là où il l'avait laissé sur son téléphone.

Choisir un flux de transfert

Avant d'activer le transfert inter-appareils, décidez comment chaque activité de votre application mobile doit s'ouvrir lorsqu'un utilisateur sélectionne la suggestion "Continuer sur Googlebook" :

Flux de transfert Cibler sur Googlebook Application idéale API Key
Transfert d'une application à une autre Application Android native s'exécutant dans une fenêtre de bureau Applications optimisées pour les grands écrans et le fenêtrage du bureau sur Google Play HandoffActivityData.Builder
Transfert d'une application à une autre avec redirection vers le Web Application Android native, si elle est installée ; sinon, une URL dans le navigateur Web par défaut Applications optimisées pour une expérience de fenêtre de bureau native, mais nécessitant un navigateur de secours fiable lorsque l'application n'est pas installée sur le Googlebook setFallbackUri
Transfert vers le Web direct URL dans le navigateur Web par défaut (ou interceptée par l'application native si elle est configurée pour les liens d'application Android) Workflows where your web application is the primary desktop experience HandoffActivityData.createWebHandoff

Activer le transfert pour une activité

La fonctionnalité Poursuivre sur est désactivée par défaut et configurée par activité. Suivez ces étapes pour chaque activité d'envoi sur le téléphone :

  1. Appelez setHandoffEnabled et transmettez true une fois que l'activité a chargé l'état requis pour le transfert. Une fois activé, le système peut demander des données de transfert lorsque l'activité est au premier plan ou passe en arrière-plan.
  2. Remplacez onHandoffActivityDataRequested pour renvoyer un objet HandoffActivityData non nul. Vous pouvez interroger isHandoffEnabled à tout moment pour vérifier si le transfert est actif pour l'activité.
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"
    }
}

État de l'activité du package pour le transfert

Le système appelle onHandoffActivityDataRequested dans deux cas de figure :

  • Transfert au premier plan : l'activité est au premier plan sur le téléphone et l'utilisateur lance un transfert depuis la barre des tâches du Googlebook.
  • Capture de l'état en arrière-plan : l'activité passe en arrière-plan sur le téléphone et le système met en cache sa charge utile de transfert au cas où l'utilisateur sélectionnerait la suggestion sur Googlebook ultérieurement.

Vérifier si la demande de transfert est active

Inspectez HandoffActivityDataRequestInfo à l'aide de isActiveRequest pour faire la distinction entre un transfert actif initié par l'utilisateur et un enregistrement en arrière-plan :

  • Si isActiveRequest renvoie true, l'utilisateur a demandé activement à poursuivre l'activité sur un autre appareil pendant qu'elle est visible.
  • Si isActiveRequest renvoie false, le système met en cache l'état lorsque l'activité s'arrête.

Si vous affichez un indicateur visuel sur le téléphone confirmant que la tâche est transférée vers le Googlebook, planifiez la mise à jour de l'UI de manière asynchrone afin que onHandoffActivityDataRequested revienne sans délai.

Associer des extras d'état et un remplacement Web

Une charge utile HandoffActivityData contient les informations suivantes :

  • Composant cible (obligatoire pour le transfert d'application à application) : transmettez this.getComponentName() (le ComponentName de l'activité) ou un ComponentName(this, TargetActivity::class.java) explicite à HandoffActivityData.Builder pour désigner l'activité qui se lance sur Google Livres.
  • Extras (facultatif) : transmettez un PersistableBundle à setExtras() contenant des identifiants et des coordonnées d'interface utilisateur (par exemple, des ID de document ouvert, des clés d'onglet actif ou des positions de défilement). Le bundle doit faire moins de 50 Ko.
  • URI de secours (facultatif) : transmettez un Uri Web à setFallbackUri afin que le Googlebook puisse ouvrir la tâche dans le navigateur par défaut si votre application Android n'est pas installée.
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"
    }
}

Configurer le transfert direct vers le Web

Si votre expérience principale sur Googlebook est une application Web, transmettez HandoffActivityParams avec setAllowHandoffWithoutPackageInstalled(true) à setHandoffEnabled et appelez HandoffActivityData.createWebHandoff au lieu d'utiliser 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)
    }
}

Recevoir et restaurer l'état sur un Googlebook

Lorsque l'utilisateur sélectionne la suggestion "Continuer sur" dans la barre des tâches Googlebook, le système lance l'ComponentName cible dans une fenêtre de bureau et ajoute les extras PersistableBundle au Intent de lancement.

Sur un Googlebook, gérez le transfert entrant en gardant à l'esprit le fenêtrage du bureau et les mises en page à plusieurs volets :

  1. Extraire les extras de transfert : dans les méthodes onCreate et onNewIntent de l'activité de réception, récupérez les extras de l'Intent entrant. La gestion de onNewIntent garantit que si votre activité utilise le mode de lancement singleTop ou est redirigée vers une fenêtre de bureau existante, la fenêtre est mise à jour avec l'élément transféré.
  2. Adapter l'état aux mises en page à plusieurs volets : un état qui occupait une vue détaillée en plein écran sur un téléphone correspond souvent à un élément sélectionné dans une mise en page de type liste/détails ou volet d'assistance sur Googlebook. Remplissez à la fois la sélection de la liste principale et le volet Détails afin que la fenêtre du bureau dispose d'un contexte complet.
  3. Conserver l'état restauré lors du redimensionnement de la fenêtre : stockez l'état transféré dans un ViewModel (SavedStateHandle) ou un rememberSaveable dans Jetpack Compose afin que le redimensionnement de la fenêtre en mode forme libre sur un Chromebook ne réinitialise pas la session restaurée.
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"
    }
}

Tester le transfert entre un téléphone et un Googlebook

Pour vérifier votre implémentation de bout en bout :

  1. Préparez les deux appareils.
    • Connectez-vous au même compte Google sur votre téléphone Android (équipé d'Android 17 ou version ultérieure) et sur votre Googlebook (ou sur un appareil de test fonctionnant en mode fenêtré).
    • Activez le Bluetooth et connectez les deux appareils au même réseau Wi-Fi.
    • Sur les deux appareils, ouvrez Paramètres > Appareils connectés > Préférences de connexion > Services inter-appareils > Continuer l'activité, suivez les invites de configuration et activez Tasks. Pour connaître toutes les conditions préalables concernant l'environnement, consultez Configurer et tester.
  2. Installer votre application
    • Pour le transfert d'appli à appli, installez votre build sur le téléphone et sur le Googlebook.
    • Pour le fallback Web ou le transfert direct vers le Web, installez votre build sur le téléphone et désinstallez l'application sur le Googlebook pour vérifier que le navigateur ouvre l'URL attendue.
  3. Déclencher le transfert
    • Sur le téléphone, ouvrez votre application et accédez à l'activité qui permet le transfert (en appelant setHandoffEnabled(true, null) pour le transfert d'application à application ou en transmettant HandoffActivityParams avec setAllowHandoffWithoutPackageInstalled(true) lors du test avec l'application désinstallée sur le Googlebook).
    • Sur le Googlebook, recherchez l'icône d'application avec le badge "Continuer sur" sur le côté droit de la barre des tâches, puis sélectionnez-la.
  4. Vérifier la restauration sur ordinateur
    • Vérifiez que votre application s'ouvre dans une fenêtre de bureau (ou un onglet de navigateur) à l'élément et à la position de défilement exacts du téléphone.
    • Redimensionnez la fenêtre de l'application sur Google Books et vérifiez que le contenu et la sélection restaurés persistent lors des transitions de classe de taille de fenêtre.

Ressources supplémentaires