Compartilhar via


Link do Telefone – Continuidade de tarefas contínua

Os dispositivos móveis Android que instalaram o pacote "Link para Windows" podem compartilhar programaticamente tarefas recentes do seu aplicativo Android para continuar em seu computador Windows (como URLs do site, links de documento, faixas de música etc.).

A continuidade de tarefas entre dispositivos está evoluindo para usar o SDK de Continuidade para oferecer uma integração nativa mais profunda com a Barra de Tarefas do Windows, melhor atendendo os clientes de maneira natural e intuitiva. Embora ainda haja suporte para a implementação original do aplicativo de continuidade de tarefas do Phone Link, para novas implementações, recomendamos usar o XDR (Currículo entre Dispositivos) no SDK de Continuidade para a integração da Barra de Tarefas do Windows. Saiba mais: XDR (Cross Device Resume) usando o SDK de Continuidade (Aplicativos Android e Windows).

O SDK de Continuidade permite experiências entre dispositivos mais perfeitas com o XDR (Currículo de Dispositivo Cruzado) exibindo ícones de continuação de tarefas para ajudá-lo a retomar suas tarefas recentes do dispositivo Android diretamente da Barra de Tarefas do Windows (sem a necessidade de contar com a interface do aplicativo Link do Telefone).

Saiba como compartilhar programaticamente tarefas recentes de seu aplicativo Android (como URLs do site, links de documento, faixas de música etc.) para um computador Windows que configurou o Link do Telefone. Esse recurso só está disponível em dispositivos com suporte para experiências de Link de Telefone.

Requisitos de cenário

As seguintes condições devem ser atendidas para que seu aplicativo Android acesse a continuidade da tarefa "Vincular ao Windows":

  • SINCRONIZAr URLs da Web válidas para serem acessíveis pelo computador Windows
  • Links de documento de nuvem de sincronização do DO para serem acessíveis pelo computador Windows
  • Sincronizar links de documentos locais para o computador Windows que devem estar acessíveis no dispositivo móvel por meio de seu aplicativo
  • NÃO sincronize mais de 60 vezes por minuto
  • NÃO sincronize conteúdo se o usuário não estiver interagindo com sua experiência de aplicativo

O Link do Telefone exibirá o conteúdo sincronizado no nó Aplicativos em "Recentemente usado" e "Sites recentes" e em um submenu de notificação.

Captura de tela do Phone Link de aplicativos e sites recentemente usados

O XDR (Currículo entre Dispositivos) usando o SDK de Continuidade (Aplicativos Android e Windows) exibirá o conteúdo sincronizado na barra de tarefas do Windows.

Captura de tela da Barra de Tarefas do Windows

Aprovação do Recurso de Acesso Limitado (LAF)

A continuidade da tarefa link de telefone é um LAF (Recurso de Acesso Limitado). Para obter acesso, você precisará obter aprovação da Microsoft para interoperar com o pacote "Link para Windows" pré-carregado em dispositivos móveis Android.

Para solicitar o acesso, envie um email wincrossdeviceapi@microsoft.com com as informações listadas abaixo.

  • Descrição da sua experiência de usuário
  • Captura de tela do seu aplicativo onde um usuário acessa nativamente a Web ou documentos
  • PackageId do seu aplicativo
  • Link do Google Play Store para seu aplicativo

Se a solicitação for aprovada, você receberá instruções sobre como desbloquear o recurso. As aprovações serão baseadas em sua comunicação, desde que seu cenário atenda aos Requisitos de cenário descritos acima.

Processamento dos dados

Usando a continuidade da tarefa Link de Telefone, a Microsoft processará e transferirá seus dados de acordo com o Contrato de Serviços da Microsoft e a Política de Privacidade da Microsoft. Os dados transferidos aos dispositivos vinculados do usuário podem ser processados por meio dos serviços de nuvem da Microsoft para garantir a transferência confiável de dados entre dispositivos. Os dados processados por esta API não são retidos pelos serviços de nuvem da Microsoft sujeitos ao controle do usuário final.

O SDK de Continuidade que você integrará em seu pacote de aplicativos garante que os dados fornecidos à API sejam tratados apenas por pacotes confiáveis da Microsoft.

Abaixo estão as diretrizes gerais e os exemplos de código para integração. Para obter diretrizes de integração detalhadas, consulte o documento Kotlin do SDK.

Declarações de manifesto do aplicativo Android

O manifesto do aplicativo é um arquivo XML que serve como um blueprint para seu aplicativo Android. O arquivo de declaração fornece informações ao sistema operacional sobre a estrutura, os componentes, as permissões, etc. do aplicativo. As declarações a seguir são necessárias para a continuidade da tarefa com "Link para Windows".

Metadados de recurso

Os aplicativos parceiros precisam primeiro registrar metadados no manifesto do aplicativo.

Para participar do contrato de contexto do aplicativo, os metadados devem ser declarados para o tipo de contexto de aplicativo com suporte. Por exemplo, para adicionar metadados do provedor de contexto do aplicativo para o recurso Entrega de Aplicativo :

<application...>
<meta-data
android:name="com.microsoft.crossdevice.applicationContextProvider"
android:value="true" />
</application>

Se o aplicativo der suporte a mais de um tipo de contexto de aplicativo, cada tipo de metadados deverá ser adicionado. Os tipos de metadados com suporte no momento incluem:

<meta-data
android:name="com.microsoft.crossdevice.browserContextProvider"
android:value="true" />

<meta-data
android:name="com.microsoft.crossdevice.applicationContextProvider"
android:value="true" />

<meta-data
android:name="com.microsoft.crossdevice.resumeActivityProvider
android:value="true" />

Para adicionar um novo tipo, o formato de nome de metadados deve ser "com.microsoft.crossdevice.xxxProvider".

Os aplicativos também devem declarar o tipo de gatilho metadados no manifesto. Essas declarações ajudam o sistema a determinar como e quando o aplicativo deve notificar Load-Time LTW (Tecelagem) sobre determinados recursos estarem ativos.

Para um gatilho auto-notificador, em que o próprio aplicativo é responsável por notificar o sistema e está habilitado em todos os dispositivos, independentemente do OEM (Fabricante de Equipamento Original), o tipo de gatilho deve ser declarado como:

<application ...
<meta-data
android:name="com.microsoft.crossdevice.trigger.PartnerApp"
android:value="the sum value of all features' binary codes" />

</application>

Para um gatilho de API do sistema, no qual o aplicativo depende de APIs do sistema para disparar o recurso "Vincular ao Windows", habilitado apenas em dispositivos OEM específicos, o tipo de gatilho deve ser declarado como:

<application ...
<meta-data
android:name="com.microsoft.crossdevice.trigger.SystemApi"
android:value="the sum value of all features' binary codes" />

</application>

Os códigos binários dos recursos agora são:

APPLICATION_CONTEXT: 1
BROWSER_HISTORY:     2
RESUME_ACTIVITY:     4

O registro de manifesto do aplicativo pode ser semelhante a este exemplo:

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 

    <application … 
 
       <!-- 
           This is the meta-data represents this app supports XDR, LTW will check  
           the package before we request app context. 
       --> 
       <meta-data 
                android:name="com.microsoft.crossdevice.resumeActivityProvider" 
                android:value="true" />

             <!-- 
           This is the meta-data represents this app supports trigger from app, the
           Value is the code of XDR feature, LTW will check if the app support partner
           app trigger when receiving trigger broadcast.
           --> 
       <meta-data 
                android:name="com.microsoft.crossdevice.trigger.PartnerApp" 
                android:value="4" />

    </application>  
</manifest> 

Exemplo de código para enviar o contexto do aplicativo

Depois que as declarações de manifesto do aplicativo tiverem sido adicionadas, os aplicativos parceiros "Link para Windows" precisarão:

  1. Determine o tempo apropriado para chamar as funções Initialize e DeInitialize para o SDK de Continuidade. Depois de chamar a função Initialize, um retorno de chamada que implementa IAppContextEventHandler deve ser disparado.

  2. Depois de inicializar o SDK de Continuidade, se onContextRequestReceived() for chamado, ele indicará que a conexão está estabelecida. Em seguida, o aplicativo pode enviar AppContext (incluindo criar e atualizar) para LTW ou excluir AppContext do LTW.

Evite enviar dados AppContextconfidenciais, como tokens de acesso. Além disso, se o tempo de vida for muito curto, ele AppContext poderá expirar antes de ser enviado para o computador. É recomendável definir um tempo de vida mínimo de pelo menos 5 minutos.

class MainActivity : AppCompatActivity() {

    private val appContextResponse = object : IAppContextResponse {
        override fun onContextResponseSuccess(response: AppContext) {
            Log.d("MainActivity", "onContextResponseSuccess")
            runOnUiThread {
                Toast.makeText(
                    this@MainActivity,
                    "Context response success: ${response.contextId}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }

        override fun onContextResponseError(response: AppContext, throwable: Throwable) {
            Log.d("MainActivity", "onContextResponseError: ${throwable.message}")
            runOnUiThread {
                Toast.makeText(
                    this@MainActivity,
                    "Context response error: ${throwable.message}",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }

    private lateinit var appContextEventHandler: IAppContextEventHandler

    private val _currentAppContext = MutableLiveData<AppContext?>()
    private val currentAppContext: LiveData<AppContext?> get() = _currentAppContext


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        LogUtils.setDebugMode(true)
        var ready = false
        val buttonSend: Button = findViewById(R.id.buttonSend)
        val buttonDelete: Button = findViewById(R.id.buttonDelete)
        val buttonUpdate: Button = findViewById(R.id.buttonUpdate)
        setButtonDisabled(buttonSend)
        setButtonDisabled(buttonDelete)
        setButtonDisabled(buttonUpdate)
        buttonSend.setOnClickListener {
            if (ready) {
                sendAppContext()
            }
        }
        buttonDelete.setOnClickListener {
            if (ready) {
                deleteAppContext()
            }
        }
        buttonUpdate.setOnClickListener {
            if (ready) {
                updateAppContext()
            }
        }
        appContextEventHandler = object : IAppContextEventHandler {
            override fun onContextRequestReceived(contextRequestInfo: ContextRequestInfo) {
                LogUtils.d("MainActivity", "onContextRequestReceived")
                ready = true
                setButtonEnabled(buttonSend)
                setButtonEnabled(buttonDelete)
                setButtonEnabled(buttonUpdate)
            }

            override fun onInvalidContextRequestReceived(throwable: Throwable) {
                Log.d("MainActivity", "onInvalidContextRequestReceived")
            }

            override fun onSyncServiceDisconnected() {
                Log.d("MainActivity", "onSyncServiceDisconnected")
                ready = false
                setButtonDisabled(buttonSend)
                setButtonDisabled(buttonDelete)
            }
        }
        // Initialize the AppContextManager
        AppContextManager.initialize(this.applicationContext, appContextEventHandler)


        // Update currentAppContext text view.
        val textView = findViewById<TextView>(R.id.appContext)
        currentAppContext.observe(this, Observer { appContext ->
            appContext?.let {
                textView.text =
                    "Current app context: ${it.contextId}\n App ID: ${it.appId}\n Created: ${it.createTime}\n Updated: ${it.lastUpdatedTime}\n Type: ${it.type}"
                Log.d("MainActivity", "Current app context: ${it.contextId}")
            } ?: run {
                textView.text = "No current app context available"
                Log.d("MainActivity", "No current app context available")
            }
        })

    }

    // Send app context to LTW
    private fun sendAppContext() {
        val appContext = AppContext().apply {
            this.contextId = generateContextId()
            this.appId = applicationContext.packageName
            this.createTime = System.currentTimeMillis()
            this.lastUpdatedTime = System.currentTimeMillis()
            // Set the type of app context, for example, resume activity.
            this.type = ProtocolConstants.TYPE_RESUME_ACTIVITY
            // Set the rest fields in appContext
            //……
        }
        _currentAppContext.value = appContext
        AppContextManager.sendAppContext(this.applicationContext, appContext, appContextResponse)
    }

    // Delete app context from LTW
    private fun deleteAppContext() {
        currentAppContext.value?.let {
            AppContextManager.deleteAppContext(
                this.applicationContext,
                it.contextId,
                appContextResponse
            )
            _currentAppContext.value = null
        } ?: run {
            Toast.makeText(this, "No resume activity to delete", Toast.LENGTH_SHORT).show()
            Log.d("MainActivity", "No resume activity to delete")
        }
    }

    // Update app context from LTW
    private fun updateAppContext() {
        currentAppContext.value?.let {
            it.lastUpdatedTime = System.currentTimeMillis()
            AppContextManager.sendAppContext(this.applicationContext, it, appContextResponse)
            _currentAppContext.postValue(it)
        } ?: run {
            Toast.makeText(this, "No resume activity to update", Toast.LENGTH_SHORT).show()
            Log.d("MainActivity", "No resume activity to update")
        }
    }

    private fun setButtonDisabled(button: Button) {
        button.isEnabled = false
        button.alpha = 0.5f
    }

    private fun setButtonEnabled(button: Button) {
        button.isEnabled = true
        button.alpha = 1.0f
    }

    override fun onDestroy() {
        super.onDestroy()
        // Deinitialize the AppContextManager
        AppContextManager.deInitialize(this.applicationContext)
    }

    private fun generateContextId(): String {
        return "${packageName}.${UUID.randomUUID()}"
    }
}

Para todos os campos obrigatórios e opcionais , consulte a Descrição do AppContext.

Descrição do AppContext

Os seguintes valores devem ser fornecidos pelos aplicativos parceiros ao enviar o contexto do aplicativo:

Chave Valor Informações adicionais
contextId [obrigatório] Usado para distingui-lo de outros contextos de aplicativo. Exclusivo para cada contexto de aplicativo. Formato: "${packageName}.${UUID.randomUUID()}"
tipo [obrigatório] Um sinalizador binário que indica qual tipo de contexto de aplicativo é enviado para o LTW. O valor deve ser consistente com requestedContextType acima
createTime[required] [FR1] Carimbo de data/hora que representa o momento de criação do contexto do aplicativo.
lastUpdatedTime[obrigatório] Carimbo de data/hora que representa o momento da última atualização do contexto do aplicativo. Sempre que algum campo de contexto do aplicativo for atualizado, o momento atualizado precisará ser registrado.
teamId [opcional] Usado para identificar a organização ou o grupo ao qual o aplicativo pertence.
intentUri [opcional] Usado para indicar qual aplicativo pode continuar o contexto do aplicativo entregue do dispositivo de origem. O comprimento máximo é de 2.083 caracteres.
appId [opcional] O pacote do aplicativo para o qual o contexto se destina.
title[opcional] O título desse contexto de aplicativo, como um nome de documento ou título de página da Web.
weblink[opcional] A URL da página da Web a ser carregada em um navegador para continuar o contexto do aplicativo. O comprimento máximo é de 2.083 caracteres.
preview[opcional] Bytes da imagem de visualização que podem representar o contexto do aplicativo
extras[opcional] Um objeto de par chave-valor que contém informações de estado específicas do aplicativo necessárias para continuar um contexto de aplicativo no dispositivo contínuo. Necessidade de fornecer quando o contexto do aplicativo tem seus dados exclusivos.
LifeTime[opcional] O tempo de vida do contexto do aplicativo em milissegundos. Usado somente para cenário contínuo, se não for definido, o valor padrão será 30 dias).

Exemplo de código de continuidade do navegador

Este exemplo destaca o uso do tipo Continuidade do Navegador , que difere de outros AppContext tipos.

class MainActivity : AppCompatActivity() {

    private val appContextResponse = object : IAppContextResponse {
        override fun onContextResponseSuccess(response: AppContext) {
            Log.d("MainActivity", "onContextResponseSuccess")
        }

        override fun onContextResponseError(response: AppContext, throwable: Throwable) {
            Log.d("MainActivity", "onContextResponseError: ${throwable.message}")
        }
    }

    private lateinit var appContextEventHandler: IAppContextEventHandler

    private val browserHistoryContext: BrowserHistoryContext = BrowserHistoryContext()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //……
        LogUtils.setDebugMode(true)
        var ready = false
        val buttonSend: Button = findViewById(R.id.buttonSend)
        val buttonDelete: Button = findViewById(R.id.buttonDelete)
        setButtonDisabled(buttonSend)
        setButtonDisabled(buttonDelete)
        buttonSend.setOnClickListener {
            if (ready) {
                sendBrowserHistory ()
            }
        }
        buttonDelete.setOnClickListener {
            if (ready) {
                clearBrowserHistory ()
            }
        }
        appContextEventHandler = object : IAppContextEventHandler {
            override fun onContextRequestReceived(contextRequestInfo: ContextRequestInfo) {
                LogUtils.d("MainActivity", "onContextRequestReceived")
                ready = true
                setButtonEnabled(buttonSend)
                setButtonEnabled(buttonDelete)
            }

            override fun onInvalidContextRequestReceived(throwable: Throwable) {
                Log.d("MainActivity", "onInvalidContextRequestReceived")
            }

            override fun onSyncServiceDisconnected() {
                Log.d("MainActivity", "onSyncServiceDisconnected")
                ready = false
                setButtonDisabled(buttonSend)
                setButtonDisabled(buttonDelete)
            }
        }
        // Initialize the AppContextManager
        AppContextManager.initialize(this.applicationContext, appContextEventHandler)
    }

    // Send browser history to LTW
    private fun sendBrowserHistory () {
        browserHistoryContext.setAppId(this.packageName)
        browserHistoryContext.addBrowserContext(System.currentTimeMillis(),
             Uri.parse("https://www.bing.com/"), "Bing Search", null
        )
        AppContextManager.sendAppContext(this.applicationContext, browserHistoryContext, appContextResponse)

    }

    // Clear browser history from LTW
         private fun clearBrowserHistory() {
        browserHistoryContext.setAppId(this.packageName)
        browserHistoryContext.setBrowserContextEmptyFlag(true)
        AppContextManager.sendAppContext(this.applicationContext, browserHistoryContext, appContextResponse)
    }

    private fun setButtonDisabled(button: Button) {
        button.isEnabled = false
        button.alpha = 0.5f
    }

    private fun setButtonEnabled(button: Button) {
        button.isEnabled = true
        button.alpha = 1.0f
    }

    override fun onDestroy() {
        super.onDestroy()
        // Deinitialize the AppContextManager
        AppContextManager.deInitialize(this.applicationContext)
    }

    //……
}

Para todos os campos obrigatórios e opcionais , consulte BrowserContext Description.

Descrição de BrowserContext

Os aplicativos parceiros podem chamar o método para adicionar o addBrowserContext histórico do navegador. Os seguintes valores devem ser fornecidos ao adicionar o histórico do navegador:

Chave Valor
browserWebUri [obrigatório] Um URI da Web que será aberto no navegador no PC (http: ou https:).
título [obrigatório] O título da página da Web.
carimbo de data/hora [obrigatório] O carimbo de data/hora em que a página da Web foi aberta pela primeira vez ou atualizada pela última vez.
favIcon [opcional] O favicon da página da Web em bytes, deve ser pequeno em geral.

Etapas de validação de integração

  1. Prepare-se garantindo que o LTW privado esteja instalado. Confirme se o LTW está conectado ao computador: como gerenciar seu dispositivo móvel em seu computador. Confirme se o LTW está conectado ao Link do Telefone: requisitos e configuração do Link de Telefone. Se depois de verificar o código QR, você não poderá entrar em LTW, abra LTW primeiro e examine o código QR dentro do aplicativo. Por fim, verifique se o aplicativo parceiro integrou o SDK de Continuidade.

  2. Valide iniciando o aplicativo e inicializando o SDK de Continuidade. Confirme se onContextRequestReceived() isso foi chamado. Uma vez onContextRequestReceived() chamado, o aplicativo pode enviar o contexto do aplicativo para LTW. Se onContextResponseSuccess() for chamado após o envio do contexto do aplicativo, a integração do SDK será bem-sucedida.

Repositório entre dispositivos do Windows no GitHub

Encontre informações sobre como integrar o Windows Cross-Device SDK ao seu projeto no repositório Windows-Cross-Device no GitHub.

Para obter uma lista de perguntas frequentes, consulte Perguntas frequentes sobre Phone Links.