Supportare la continuità cross-device su Googlebook

Gli utenti spesso iniziano le attività sul proprio smartphone Android, ad esempio rivedere un documento, organizzare i messaggi o sfogliare un assortimento di articoli, e poi vogliono passare a un Googlebook con il suo display più grande, la tastiera fisica e lo spazio di lavoro multi-finestra.

La funzionalità Continua su in Android 17 (livello API 37) e versioni successive consente alla barra delle app Googlebook di visualizzare un'icona dell'app (chiamata suggerimento) per l'attività che un utente stava eseguendo sul proprio smartphone nelle vicinanze. Con un solo clic, gli utenti possono continuare la loro attività sul Googlebook.

L'implementazione del trasferimento cross-device consente alla tua app di trasferire il contesto dell'attività in modo che l'utente riprenda il flusso di lavoro, nella tua app Android adattiva o nel browser desktop, esattamente da dove aveva interrotto sullo smartphone.

Scegliere un flusso di trasferimento

Prima di attivare il trasferimento cross-device, decidi come deve aprirsi ogni attività nell'app per smartphone quando un utente seleziona il suggerimento Continua su Googlebook:

Flusso di trasferimento Targeting su Googlebook Ideale per API Key
Trasferimento da un'app all'altra App per Android nativa in esecuzione in una finestra del desktop App ottimizzate per schermi di grandi dimensioni e visualizzazione a finestre sul desktop su Google Play HandoffActivityData.Builder
Trasferimento da app ad app con fallback web App per Android nativa, se installata; in caso contrario, un URL nel browser desktop predefinito App ottimizzate per un'esperienza nativa con finestre del desktop, ma che richiedono un fallback affidabile del browser quando l'app non è installata su Googlebook setFallbackUri
Trasferimento diretto al web URL nel browser desktop predefinito (o intercettato dall'app nativa se configurata per i link alle app per Android) Flussi di lavoro in cui l'applicazione web è l'esperienza desktop principale HandoffActivityData.createWebHandoff

Attivare il trasferimento per un'attività

Continua su è disattivata per impostazione predefinita e viene configurata per attività. Segui questi passaggi in ogni attività di invio sullo smartphone:

  1. Chiama setHandoffEnabled e passa true una volta che l'attività ha caricato lo stato richiesto per il trasferimento. Una volta attivato, il sistema può richiedere dati di trasferimento mentre l'attività è in primo piano o si sposta in background.
  2. Esegui l'override di onHandoffActivityDataRequested per restituire un oggetto HandoffActivityData non nullo. Puoi eseguire query su isHandoffEnabled in qualsiasi momento per verificare se il trasferimento è attivo per l'attività.
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"
    }
}

Stato dell'attività del pacchetto per il trasferimento

Il sistema chiama onHandoffActivityDataRequested in due scenari:

  • Trasferimento in primo piano:l'attività è in primo piano sullo smartphone e l'utente avvia un trasferimento dalla barra delle app di Googlebook.
  • Acquisizione dello stato in background: l'attività viene spostata in background sullo smartphone e il sistema memorizza nella cache il payload di trasferimento nel caso in cui l'utente selezioni il suggerimento su Googlebook in un secondo momento.

Controllare se la richiesta di trasferimento è attiva

Ispeziona HandoffActivityDataRequestInfo utilizzando isActiveRequest per distinguere tra un trasferimento attivo avviato dall'utente e un salvataggio in background:

  • Se isActiveRequest restituisce true, l'utente ha richiesto attivamente di continuare su un altro dispositivo mentre l'attività è visibile.
  • Se isActiveRequest restituisce false, il sistema memorizza nella cache lo stato quando l'attività si interrompe.

Se visualizzi un indicatore visivo sullo smartphone che conferma che l'attività si sta spostando su Googlebook, pianifica l'aggiornamento della UI in modo asincrono in modo che onHandoffActivityDataRequested venga restituito senza ritardi.

Allegare gli extra statali e un fallback web

Un payload HandoffActivityData contiene le seguenti informazioni:

  • Componente target (obbligatorio per il trasferimento da app ad app): passa this.getComponentName() (l'ComponentName dell'attività) o un ComponentName(this, TargetActivity::class.java) esplicito a HandoffActivityData.Builder per indicare quale attività viene avviata su Googlebook.
  • Extra (facoltativo): passa un PersistableBundle a setExtras() contenente identificatori e coordinate dell'interfaccia utente (ad esempio, ID documento aperto, tasti delle schede attive o posizioni di scorrimento). Il bundle deve avere dimensioni inferiori a 50 KB.
  • URI di riserva (facoltativo): passa un Uri web a setFallbackUri in modo che Googlebook possa aprire l'attività nel browser predefinito se l'app per Android non è installata.
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"
    }
}

Configurare il trasferimento diretto al web

Se la tua esperienza desktop principale su Googlebook è un'applicazione web, passa HandoffActivityParams con setAllowHandoffWithoutPackageInstalled(true) a setHandoffEnabled e chiama HandoffActivityData.createWebHandoff anziché utilizzare 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)
    }
}

Ricevere e ripristinare lo stato su Googlebook

Quando l'utente seleziona il suggerimento Continua su nella barra delle app di Googlebook, il sistema avvia l'intent ComponentName di destinazione in una finestra del desktop e allega gli extra PersistableBundle all'intent di avvio Intent.

Su Googlebook, gestisci il trasferimento in entrata tenendo presente le finestre delle app e i layout multipanello:

  1. Estrai gli extra del trasferimento:sia nel metodo onCreate che in quello onNewIntent dell'attività ricevente, recupera gli extra dall'intent Intent in arrivo. La gestione di onNewIntent garantisce che, se l'attività utilizza la modalità di avvio singleTop o indirizza a una finestra desktop esistente, la finestra venga aggiornata all'elemento trasferito.
  2. Adatta lo stato ai layout a più riquadri: uno stato che occupava una visualizzazione dettagliata a schermo intero su uno smartphone spesso corrisponde a un elemento selezionato all'interno di un layout elenco-dettagli o riquadro di supporto su Googlebook. Compila sia la selezione dell'elenco principale sia il riquadro dei dettagli in modo che la finestra del computer abbia un contesto completo.
  3. Preserva lo stato ripristinato durante il ridimensionamento della finestra:memorizza lo stato trasferito in un ViewModel (SavedStateHandle) o rememberSaveable in Jetpack Compose in modo che il ridimensionamento della finestra in formato libero su Googlebook non reimposti la sessione ripristinata.
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"
    }
}

Testare il trasferimento tra uno smartphone e Googlebook

Per verificare l'implementazione end-to-end:

  1. Prepara entrambi i dispositivi
    • Accedi allo stesso Account Google sia sullo smartphone Android (con Android 17 o versioni successive) sia su Googlebook (o su un dispositivo di test in modalità finestra desktop).
    • Attiva il Bluetooth e connetti entrambi i dispositivi alla stessa rete Wi-Fi.
    • Su entrambi i dispositivi, apri Impostazioni > Dispositivi connessi > Preferenze di connessione > Servizi cross-device > Continua l'attività, segui le istruzioni di configurazione e attiva Attività. Per i prerequisiti completi dell'ambiente, vedi Configurazione e test.
  2. Installare l'app
    • Per il trasferimento da app ad app, installa la build sia sullo smartphone che su Googlebook.
    • Per il fallback web o il trasferimento diretto al web, installa la build sullo smartphone e lascia l'app disinstallata su Googlebook per verificare che il browser apra l'URL previsto.
  3. Attivare il trasferimento
    • Sullo smartphone, apri l'app e vai all'attività che consente il trasferimento (chiamata setHandoffEnabled(true, null) per il trasferimento da app ad app, o passaggio di HandoffActivityParams con setAllowHandoffWithoutPackageInstalled(true) durante il test con l'app disinstallata su Googlebook).
    • Su Googlebook, controlla il lato destro della barra delle app per trovare l'icona dell'app con il badge Continua su e selezionala.
  4. Verifica il ripristino del computer
    • Verifica che l'app si apra in una finestra del computer (o scheda del browser) esattamente sull'elemento e sulla posizione di scorrimento dello smartphone.
    • Ridimensiona la finestra dell'app su Googlebook e verifica che i contenuti ripristinati e la selezione persistano durante le transizioni tra le classi di dimensioni delle finestre.

Risorse aggiuntive