Tautan Telepon - Kelangsungan tugas tanpa hambatan

Perangkat seluler Android yang telah menginstal paket "Tautan ke Windows" dapat secara terprogram berbagi tugas terbaru dari aplikasi Android Anda untuk dilanjutkan di PC Windows Anda (seperti URL situs web, tautan dokumen, trek musik, dll.).

Kelangsungan Tugas Lintas Perangkat berkembang untuk menggunakan SDK Kelangsungan untuk menawarkan integrasi asli yang lebih dalam dengan Windows Taskbar, lebih baik melayani pelanggan dengan cara alami dan intuitif. Meskipun implementasi asli aplikasi kelangsungan tugas Phone Link masih didukung, untuk implementasi baru, sebaiknya gunakan Cross Device Resume (XDR) dalam SDK Kelangsungan untuk integrasi Windows Taskbar. Pelajari lebih lanjut: Resume Lintas Perangkat (XDR) menggunakan Continuity SDK (Aplikasi Android dan Windows).

Continuity SDK memungkinkan pengalaman lintas perangkat yang lebih mulus dengan Cross Device Resume (XDR) yang menampilkan ikon kelanjutan tugas untuk membantu Anda melanjutkan tugas perangkat Android baru-baru ini langsung dari Windows Taskbar (tanpa perlu mengandalkan antarmuka aplikasi Phone Link).

Pelajari cara berbagi tugas terbaru secara terprogram dari aplikasi Android Anda (seperti URL situs web, tautan dokumen, trek musik, dll.) ke PC Windows yang telah menyiapkan Phone Link. Fitur ini hanya tersedia di perangkat yang didukung untuk pengalaman Phone Link.

Persyaratan skenario

Kondisi berikut harus dipenuhi agar aplikasi Android Anda dapat mengakses kelangsungan tugas "Tautkan ke Windows":

  • SINKRONISASI DO URL web yang valid agar dapat diakses oleh PC Windows
  • Tautan dokumen cloud sinkronisasi DO agar dapat diakses oleh PC Windows
  • MENYinkronkan tautan dokumen lokal ke PC Windows yang harus dapat diakses di perangkat seluler melalui aplikasi Anda
  • JANGAN sinkronkan lebih dari 60 kali per menit
  • JANGAN sinkronkan konten jika pengguna tidak terlibat dengan pengalaman aplikasi Anda

Tautan Telepon akan menampilkan konten yang disinkronkan di node Aplikasi di bagian "Baru digunakan" dan "Situs web terbaru" dan dalam flyout pemberitahuan.

Koneksi ke Ponsel cuplikan layar aplikasi dan situs web yang baru digunakan

Cross Device Resume (XDR) menggunakan Continuity SDK (Aplikasi Android dan Windows) akan menampilkan konten yang disinkronkan di Taskbar Windows.

Cuplikan layar Taskbar Windows

Persetujuan Fitur Akses Terbatas (LAF)

Kelangsungan tugas Phone Link adalah Fitur Akses Terbatas (LAF). Untuk mendapatkan akses, Anda harus mendapatkan persetujuan dari Microsoft untuk beroperasi dengan paket "Tautan ke Windows" yang telah dimuat sebelumnya di perangkat seluler Android.

Untuk meminta akses, kirim email wincrossdeviceapi@microsoft.com dengan informasi yang tercantum di bawah ini.

  • Deskripsi pengalaman pengguna Anda
  • Cuplikan layar aplikasi Anda di mana pengguna secara asli mengakses web atau dokumen
  • PackageId aplikasi Anda
  • Tautan Google Play Store untuk aplikasi Anda

Jika permintaan disetujui, Anda akan menerima instruksi tentang cara membuka kunci fitur. Persetujuan akan didasarkan pada komunikasi Anda, asalkan skenario Anda memenuhi Persyaratan Skenario yang diuraikan di atas.

Penanganan Data

Dengan menggunakan kelangsungan tugas Phone Link, Microsoft akan memproses dan mentransfer data Anda sesuai dengan Perjanjian Layanan Microsoft dan Pernyataan Privasi Microsoft. Data yang ditransfer ke perangkat tertaut pengguna dapat diproses melalui layanan cloud Microsoft untuk memastikan transfer data yang andal antar perangkat. Data yang ditangani oleh API ini tidak dipertahankan oleh layanan cloud Microsoft yang tunduk pada kontrol pengguna akhir.

SDK Kelangsungan yang akan Anda integrasikan dalam paket aplikasi memastikan bahwa data yang diberikan ke API hanya ditangani oleh paket Microsoft tepercaya.

Di bawah ini adalah panduan umum dan sampel kode untuk integrasi. Untuk panduan integrasi terperinci, lihat dokumen Kotlin SDK.

Deklarasi manifes aplikasi Android

Manifes aplikasi adalah file XML yang berfungsi sebagai cetak biru untuk aplikasi Android Anda. File deklarasi menyediakan informasi ke sistem operasi tentang struktur, komponen, izin, dll aplikasi Anda. Deklarasi berikut diperlukan untuk kelangsungan tugas dengan "Tautkan ke Windows".

Metadata Fitur

Aplikasi mitra harus terlebih dahulu mendaftarkan meta-data dalam manifes aplikasi Anda.

Untuk berpartisipasi dalam kontrak konteks aplikasi, meta-data harus dideklarasikan untuk jenis konteks aplikasi yang didukung. Misalnya, untuk menambahkan metadata penyedia konteks aplikasi untuk fitur Handoff Aplikasi :

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

Jika aplikasi Anda mendukung lebih dari satu jenis konteks aplikasi, setiap jenis meta-data harus ditambahkan. Jenis meta-data yang saat ini didukung, meliputi:

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

Untuk menambahkan jenis baru, format nama meta-data harus "com.microsoft.crossdevice.xxxProvider".

Aplikasi juga harus mendeklarasikan jenis pemicu meta-data dalam manifes. Deklarasi ini membantu sistem menentukan bagaimana dan kapan aplikasi harus memberi tahu Load-Time Weaving (LTW) tentang fitur tertentu yang aktif.

Untuk pemicu pemberitahuan mandiri, di mana aplikasi itu sendiri bertanggung jawab untuk memberi tahu sistem dan diaktifkan pada semua perangkat, terlepas dari Original Equipment Manufacturer (OEM), jenis pemicu harus dinyatakan sebagai:

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

</application>

Untuk pemicu API sistem, di mana aplikasi bergantung pada API sistem untuk memicu fitur "Tautan ke Windows", hanya diaktifkan pada perangkat OEM tertentu, jenis pemicu harus dinyatakan sebagai:

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

</application>

Kode biner fitur sekarang:

APPLICATION_CONTEXT: 1
BROWSER_HISTORY:     2
RESUME_ACTIVITY:     4

Pendaftaran manifes aplikasi mungkin terlihat seperti contoh ini:

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

Sampel kode untuk mengirim konteks aplikasi

Setelah deklarasi manifes aplikasi ditambahkan, aplikasi mitra "Tautkan ke Windows" harus:

  1. Tentukan waktu yang sesuai untuk memanggil fungsi Inisialisasi dan DeInitialisasi untuk SDK Kontinuitas. Setelah memanggil fungsi Inisialisasi IAppContextEventHandler , panggilan balik yang menerapkan harus dipicu.

  2. Setelah menginisialisasi SDK Kontinuitas, jika onContextRequestReceived() dipanggil, itu menunjukkan koneksi dibuat. Aplikasi kemudian dapat mengirim AppContext (termasuk buat dan perbarui) ke LTW atau menghapus AppContext dari LTW.

Pastikan untuk menghindari pengiriman data sensitif apa pun di AppContext, seperti token akses. Selain itu, jika masa pakai diatur terlalu pendek, mungkin kedaluwarsa AppContext sebelum dikirim ke PC. Disarankan untuk menetapkan masa pakai minimum setidaknya 5 menit.

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

Untuk semua bidang yang diperlukan dan opsional , lihat Deskripsi AppContext.

Deskripsi AppContext

Nilai berikut harus disediakan oleh aplikasi mitra saat mengirim konteks aplikasi:

Tombol Nilai Informasi tambahan
contextId [wajib] Digunakan untuk membedakannya dari konteks aplikasi lain. Unik untuk setiap konteks aplikasi. Format: "${packageName}.${UUID.randomUUID()}"
type [required] Bendera biner yang menunjukkan jenis konteks aplikasi apa yang dikirim ke LTW. Nilai harus konsisten dengan requestedContextType di atas
createTime[required] [FR1] Tanda waktu yang mewakili waktu pembuatan konteks aplikasi.
lastUpdatedTime[required] Tanda waktu yang mewakili waktu terakhir yang diperbarui dari konteks aplikasi. Setiap kali bidang konteks aplikasi diperbarui, waktu yang diperbarui perlu direkam.
teamId [opsional] Digunakan untuk mengidentifikasi organisasi atau grup tempat aplikasi berada.
intentUri [opsional] Digunakan untuk menunjukkan aplikasi mana yang dapat melanjutkan konteks aplikasi yang diserahkan dari perangkat asal. Panjang maksimum adalah 2083 karakter.
appId [opsional] Paket aplikasi konteksnya adalah untuk.
title[opsional] Judul konteks aplikasi ini, seperti nama dokumen atau judul halaman web.
weblink[opsional] URL halaman web yang akan dimuat di browser untuk melanjutkan konteks aplikasi. Panjang maksimum adalah 2083 karakter.
pratinjau[opsional] Byte gambar pratinjau yang dapat mewakili konteks aplikasi
extras[opsional] Objek pasangan kunci-nilai yang berisi informasi status khusus aplikasi diperlukan untuk melanjutkan konteks aplikasi pada perangkat yang berkelanjutan. Perlu disediakan saat konteks aplikasi memiliki data uniknya.
LifeTime[opsional] Masa pakai konteks aplikasi dalam milidetik. Hanya digunakan untuk skenario yang sedang berlangsung, jika tidak diatur, nilai defaultnya adalah 30 hari).

Sampel kode Kelangsungan Browser

Sampel ini menyoroti penggunaan jenis Kelangsungan Browser , yang berbeda dari jenis lain AppContext .

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

    //……
}

Untuk semua bidang yang diperlukan dan opsional , lihat BrowserContext Description.

Deskripsi BrowserContext

Aplikasi mitra dapat memanggil addBrowserContext metode untuk menambahkan riwayat browser. Nilai berikut harus disediakan saat menambahkan riwayat browser:

Tombol Nilai
browserWebUri [wajib] URI web yang akan terbuka di browser di PC (http: atau https:).
judul [wajib] Judul halaman web.
tanda waktu [wajib] Tanda waktu yang pertama kali dibuka halaman web atau terakhir disegarkan.
favIcon [opsional] Favicon halaman web dalam byte, harus kecil secara umum.

Langkah-langkah validasi integrasi

  1. Siapkan dengan memastikan bahwa LTW privat diinstal. Konfirmasikan bahwa LTW terhubung ke PC: Cara mengelola perangkat seluler Anda di PC Anda. Konfirmasikan bahwa LTW tersambung ke Phone Link: Persyaratan dan penyiapan Phone Link. Jika setelah memindai kode QR, Anda tidak dapat melompat ke LTW, buka LTW terlebih dahulu dan pindai kode QR dalam aplikasi. Terakhir, verifikasi bahwa aplikasi mitra telah mengintegrasikan SDK Kelangsungan.

  2. Validasi dengan meluncurkan aplikasi dan menginisialisasi SDK Kelangsungan. Konfirmasikan bahwa onContextRequestReceived() dipanggil. Setelah onContextRequestReceived() dipanggil, aplikasi dapat mengirim konteks aplikasi ke LTW. Jika onContextResponseSuccess() dipanggil setelah mengirim konteks aplikasi, integrasi SDK berhasil.

Repositori Windows Cross-Device di GitHub

Temukan informasi tentang mengintegrasikan Windows Cross-Device SDK ke dalam proyek Anda di repositori Windows-Cross-Device di GitHub.

Untuk daftar FAQ, lihat Koneksi ke Ponsel Tanya Jawab Umum.