Telefonní propojení – kontinuita úkolů bez potíží

Mobilní zařízení s Androidem, která nainstalovala balíček "Odkaz na Windows", můžou programově sdílet nedávné úkoly z aplikace pro Android, aby pokračovala na počítači s Windows (například adresy URL webu, odkazy na dokumenty, hudební skladby atd.).

Cross Device Task Continuity se vyvíjí tak, že využívá SDK pro kontinuitu pro nabídku hlubší nativní integrace s Panelem úloh Windows zákazníkům přirozeným a intuitivním způsobem. I když je stále podporována původní implementace aplikace pro kontinuitu úloh Phone Link, pro nové implementace doporučujeme použít funkci XDR (Cross Device Resume) v sadě SDK pro kontinuitu pro integraci hlavního panelu Windows. Další informace: Pokračování mezi zařízeními (XDR) pomocí Continuity SDK (aplikace pro Android a Windows)

Sada SDK pro kontinuitu umožňuje plynulejší prostředí napříč zařízeními pomocí funkce XDR (Cross Device Resume), která zobrazuje ikony pokračování úloh, které vám pomůžou pokračovat v posledních úlohách zařízení s Androidem přímo z hlavního panelu Windows (bez nutnosti spoléhat se na rozhraní aplikace Phone Link).

Naučte se programově sdílet poslední úkoly z aplikace pro Android (například adresy URL webu, odkazy na dokumenty, hudební skladby atd.) na počítač s Windows, který nastavil Phone Link. Tato funkce je dostupná jenom na podporovaných zařízeních pro funkce Phone Link.

Požadavky na scénář

Aby vaše aplikace pro Android měla přístup k pokračování úloh Propojení s Windows, musí být splněny následující podmínky:

  • Proveďte synchronizaci platných webových adres URL, které budou přístupné z počítače s Windows.
  • Proveďte synchronizaci cloudových odkazů na dokumenty, aby byly přístupné z PC s Windows
  • Nezapomeňte synchronizovat odkazy na místní dokumenty na počítač s Windows, aby byly přístupné na mobilním zařízení prostřednictvím vaší aplikace.
  • NEsynchronizovat více než 60krát za minutu
  • NEsynchronizujte obsah, pokud se uživatel nezapojuje do prostředí vaší aplikace.

Odkaz na telefon zobrazí váš synchronizovaný obsah v uzlu Aplikace pod položkami "Naposledy použité" a "Poslední weby" a ve vyskakovacím oznámení.

Snímek obrazovky s odkazem na telefon s nedávno používanými aplikacemi a weby

Obnovení mezi zařízeními (XDR) s použitím sady SDK Continuity (aplikace pro Android a Windows) zobrazí váš synchronizovaný obsah na hlavním panelu Windows.

Snímek obrazovky hlavního panelu Windows

Schválení funkce omezeného přístupu (LAF)

Kontinuita úloh Phone Link je funkce s omezeným přístupem (LAF). Pokud chcete získat přístup, budete muset získat schválení od Microsoftu, abyste mohli spolupracovat s balíčkem Odkaz na Windows, který je předinstalovaný na mobilních zařízeních s Androidem.

Pokud chcete požádat o přístup, pošlete e-mail wincrossdeviceapi@microsoft.com s níže uvedenými informacemi.

  • Popis uživatelského prostředí
  • Snímek obrazovky aplikace, kde uživatel nativně přistupuje k webu nebo dokumentům
  • PackageId vaší aplikace
  • Odkaz na obchod Google Play pro vaši aplikaci

Pokud je žádost schválená, obdržíte pokyny k odemknutí funkce. Schválení budou vycházet z vaší komunikace za předpokladu, že váš scénář splňuje výše uvedené požadavky na scénář .

Zpracování dat

Pomocí kontinuity úkolů Phone Link bude Společnost Microsoft zpracovávat a přenášet vaše data v souladu se smlouvou o poskytování služeb společnosti Microsoft a prohlášením o zásadách ochrany osobních údajů společnosti Microsoft. Data přenášená do propojených zařízení uživatele se můžou zpracovávat prostřednictvím cloudových služeb Microsoftu, aby se zajistil spolehlivý přenos dat mezi zařízeními. Data zpracovávaná tímto rozhraním API se neuchovávají cloudovými službami Microsoftu, které podléhají řízení koncových uživatelů.

Sada SDK pro kontinuitu, kterou integrujete do balíčku aplikace, zajišťuje, že data poskytnutá rozhraní API se zpracovávají pouze důvěryhodnými balíčky Microsoftu.

Níže jsou uvedené obecné pokyny a ukázky kódu pro integraci. Najdete podrobné pokyny k integraci v dokumentaci SDK Kotlinu.

Deklarace manifestu aplikace pro Android

Manifest aplikace je soubor XML, který slouží jako podrobný plán pro vaši aplikaci pro Android. Soubor deklarace poskytuje operačnímu systému informace o struktuře, komponentách, oprávněních atd. Následující deklarace jsou vyžadovány pro kontinuitu úkolů s odkazem na Windows.

Metadata funkcí

Partnerské aplikace musí nejprve zaregistrovat metadata v manifestu aplikace.

Aby bylo možné se účastnit kontraktu kontextu aplikace, musí být metadata deklarována pro podporovaný typ kontextu aplikace. Pokud chcete například přidat metadata poskytovatele kontextu aplikace pro funkci Předání aplikace :

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

Pokud vaše aplikace podporuje více než jeden typ kontextu aplikace, musí se přidat každý typ meta-dat. Mezi aktuálně podporované typy meta-dat patří:

<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" />

Chcete-li přidat nový typ, formát názvu meta-dat by měl být "com.microsoft.crossdevice.xxxProvider".

Aplikace musí také deklarovat metadata typu triggeru v manifestu. Tyto deklarace pomáhají systému určit, kdy a jak má aplikace informovat o aktivaci určitých funkcí pro Load-Time Weaving (LTW).

V případě triggeru s automatickým oznámením, kdy samotná aplikace zodpovídá za oznamování systému a je povolená na všech zařízeních bez ohledu na výrobce OEM (Original Equipment Manufacturer), by měl být typ triggeru deklarován takto:

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

</application>

V případě triggeru systémového rozhraní API, ve kterém aplikace spoléhá na systémová rozhraní API k aktivaci funkce "Odkaz na Windows", povolená pouze na konkrétních zařízeních OEM, by se typ triggeru měl deklarovat takto:

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

</application>

Binární kódy funkcí jsou nyní:

APPLICATION_CONTEXT: 1
BROWSER_HISTORY:     2
RESUME_ACTIVITY:     4

Registrace manifestu aplikace může vypadat jako v tomto příkladu:

<?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> 

Ukázka kódu pro odesílání kontextu aplikace

Po přidání deklarací manifestu aplikace bude potřeba vytvořit odkaz na partnerské aplikace pro Windows:

  1. Určete vhodné načasování pro volání funkcí Initialize a DeInitialize pro sadu SDK pro kontinuitu. Po volání funkce Initialize by měl být aktivován zpětný volací mechanismus, který implementuje IAppContextEventHandler.

  2. Po inicializaci sady SDK pro kontinuitu, pokud je volána onContextRequestReceived(), to značí navázání připojení. Aplikace pak může odeslat AppContext (včetně vytvoření a aktualizace) do LTW nebo odstranit AppContext z LTW.

Nezapomeňte se vyhnout odesílání citlivých dat, jako jsou AppContext, například přístupové tokeny. Kromě toho platí, že pokud je doba života nastavená příliš krátká, AppContext platnost může vypršet před odesláním do počítače. Doporučuje se nastavit minimální životnost nejméně 5 minut.

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()}"
    }
}

Pro všechna povinná a volitelná pole se podívejte na popis AppContext.

Popis AppContext

Při odesílání kontextu aplikace by měly partnerské aplikace poskytovat následující hodnoty:

Klíč Hodnota Další informace
contextId [vyžadováno] Používá se k odlišení od jiných kontextů aplikace. Jedinečné pro každý kontext aplikace. Formát: "${packageName}.${UUID.randomUUID()}"
typ [povinné] Binární příznak, který určuje, jaký typ kontextu aplikace je odeslán do LTW. Hodnota by měla být konzistentní s požadovaným typemContextType výše.
createTime[required] [FR1] Časové razítko představující čas vytvoření kontextu aplikace
lastUpdatedTime[required] Časové razítko představující čas poslední aktualizace kontextu aplikace Kdykoli se aktualizují jakákoli pole kontextu aplikace, je potřeba zaznamenat aktualizovaný čas.
teamId [volitelné] Používá se k identifikaci organizace nebo skupiny, do které aplikace patří.
intentUri [volitelné] Používá se k označení, která aplikace může pokračovat v kontextu aplikace předávané z původního zařízení. Maximální délka je 2083 znaků.
appId [volitelné] Balíček aplikace, pro který je kontext určen.
title[optional] Název tohoto kontextu aplikace, například název dokumentu nebo název webové stránky.
weblink[volitelné] Adresa URL webové stránky, která se má načíst v prohlížeči, aby pokračovala v kontextu aplikace. Maximální délka je 2083 znaků.
Preview[volitelné] Bajty obrázku ve verzi Preview, který může představovat kontext aplikace
extra[volitelné] Objekt páru klíč-hodnota obsahující informace o stavu specifické pro aplikaci, které jsou potřeba k pokračování kontextu aplikace na pokračujícím zařízení. Je potřeba zadat údaje, když kontext aplikace obsahuje jedinečná data.
LifeTime[volitelné] Životnost kontextu aplikace v milisekundách. Používá se pouze pro probíhající scénář, pokud není nastavená, výchozí hodnota je 30 dnů).

Ukázka kódu kontinuity prohlížeče

Tato ukázka zvýrazňuje použití typu Kontinuita prohlížeče , který se liší od jiných AppContext typů.

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)
    }

    //……
}

Pro všechna povinná a nepovinná pole viz BrowserContext Popis.

Popis BrowserContextu

Partnerské aplikace můžou volat metodu addBrowserContext pro přidání historie prohlížeče. Při přidávání historie prohlížeče by se měly poskytnout následující hodnoty:

Klíč Hodnota
browserWebUri [povinné] Webový identifikátor URI, který se otevře v prohlížeči na počítači (http: nebo https:).
název [required] Název webové stránky.
časové razítko [povinné] Časový údaj, kdy byla webová stránka poprvé otevřena nebo naposledy aktualizována.
favIcon [volitelné] Favicon webové stránky v bajtech, by měl být obecně malý.

Kroky ověření integrace

  1. Připravte se tím, že zajistíte instalaci privátního LTW. Ověřte, že je LTW připojený k počítači: Jak spravovat mobilní zařízení na počítači. Ověřte, že je LTW připojený k telefonnímu propojení: požadavky na telefonní propojení a nastavení. Pokud po naskenování kódu QR nemůžete přejít na LTW, nejdřív otevřít LTW a naskenovat kód QR v aplikaci. Nakonec ověřte, že partnerová aplikace integrovala sadu SDK pro kontinuitu.

  2. Ověřte spuštěním aplikace a inicializací sady SDK pro kontinuitu. Potvrďte, že onContextRequestReceived() je voláno. Po onContextRequestReceived() zavolání může aplikace odeslat kontext aplikace do LTW. Pokud onContextResponseSuccess() se volá po odeslání kontextu aplikace, integrace sady SDK je úspěšná.

Repozitář Windows napříč zařízeními na GitHubu

Najděte informace o integraci sady WINDOWS SDK pro různé zařízení do projektu v úložišti Windows-Cross-Device na GitHubu.

Seznam nejčastějších dotazů najdete v tématu Nejčastější dotazy k odkazu na telefon.