Salin dan tempel

Android menyediakan framework berbasis papan klip yang canggih untuk penyalinan dan penempelan. Cloud SQL mendukung dan tipe data kompleks, termasuk string teks, struktur data kompleks, teks, dan aliran biner data, dan aset aplikasi. Data teks sederhana disimpan langsung di {i>clipboard<i}, namun data disimpan sebagai referensi yang diselesaikan oleh aplikasi penempelan dengan penyedia konten. Menyalin dan menempel berfungsi di dalam aplikasi dan di antara aplikasi yang menerapkan Google Workspace for Education.

Karena bagian dari kerangka kerja ini menggunakan penyedia konten, dokumen ini mengasumsikan bahwa Anda telah memahami Android Content Provider API, yang dijelaskan dalam Penyedia konten.

Pengguna mengharapkan masukan saat menyalin konten ke papan klip, jadi selain kerangka kerja yang mendukung salin dan tempel, Android menampilkan UI default kepada pengguna saat menyalin di Android 13 (API level 33) dan lebih tinggi. Karena fitur ini, ada risiko notifikasi duplikat. Anda dapat mempelajari {i>edge case<i} ini pada Menghindari notifikasi duplikat bagian.

Animasi yang menampilkan notifikasi papan klip Android 13
Gambar 1. UI yang ditampilkan saat konten memasuki papan klip di Android 13 ke atas.

Berikan masukan secara manual kepada pengguna saat menyalin di Android 12L (API level 32) dan yang lebih lama. Lihat rekomendasi untuk hal ini dalam dokumen ini.

Framework papan klip

Saat Anda menggunakan framework papan klip, masukkan data ke dalam objek klip, lalu tempatkan objek klip pada papan klip di seluruh sistem. Objek klip dapat menggunakan salah satu dari tiga bentuk berikut:

Teks
String teks. Letakkan string langsung di objek klip, yang kemudian Anda letakkan di papan klip. Untuk menempelkan string, dapatkan objek klip dari papan klip, lalu salin string ke dalam penyimpanan aplikasi Anda.
URI
Objek Uri yang mewakili setiap dalam bentuk URI. Fungsi ini terutama untuk menyalin data yang kompleks dari penyedia konten. Untuk menyalin masukkan objek Uri ke dalam objek klip, lalu tempatkan objek klip ke papan klip. Untuk menempelkan data, dapatkan objek klip, dapatkan objek Uri, me-resolve ke sumber data, seperti penyedia konten, dan menyalin data dari ke dalam penyimpanan aplikasi Anda.
Intent
Intent. Ini mendukung penyalinan pintasan aplikasi. Untuk menyalin data, buat Intent, masukkan dalam objek klip, dan letakkan objek klip di papan klip. Untuk menempelkan data, dapatkan objek klip, lalu salin objek Intent ke elemen area memori utama.

Papan klip hanya menyimpan satu objek klip dalam satu waktu. Saat aplikasi menempatkan objek klip di papan klip, objek klip sebelumnya akan hilang.

Jika Anda ingin mengizinkan pengguna menempelkan data ke aplikasi, Anda tidak perlu menangani semua jenis layanan otomatis dan data skalabel. Anda dapat memeriksa data di papan klip sebelum memberikan opsi kepada pengguna untuk menempelkannya. Selain memiliki bentuk data tertentu, objek klip juga berisi metadata yang memberi tahu Anda jenis MIME jenis data yang tersedia. Metadata ini membantu Anda memutuskan apakah aplikasi dapat melakukan sesuatu yang berguna dengan data papan klip. Misalnya, jika memiliki aplikasi yang utamanya menangani teks, Anda mungkin ingin mengabaikan objek klip yang berisi URI atau maksud.

Anda mungkin juga ingin mengizinkan pengguna menempelkan teks, apa pun bentuk datanya di papan klip. Kepada melakukannya, memaksa data {i>clipboard<i} ke dalam representasi teks, dan kemudian menempelkan teks ini. Ini adalah yang dijelaskan di bagian Mengonversi papan klip ke teks.

Kelas papan klip

Bagian ini menjelaskan class yang digunakan oleh framework papan klip.

ClipboardManager

Papan klip sistem Android diwakili oleh Class ClipboardManager. Jangan buat instance class ini secara langsung. Sebaliknya, dapatkan referensi untuk data tersebut dengan memanggil getSystemService(CLIPBOARD_SERVICE).

ClipData, ClipData.Item, dan ClipDescription

Untuk menambahkan data ke {i>clipboard<i}, buat Objek ClipData yang berisi deskripsi data dan data itu sendiri. Papan klip menyimpan satu ClipData pada baik. ClipData berisi Objek ClipDescription dan satu atau beberapa Objek ClipData.Item.

Objek ClipDescription berisi metadata tentang klip. Secara khusus, dapat berisi array jenis MIME yang tersedia untuk data klip. Selain itu, pada Android 12 (level API 31) dan yang lebih tinggi, metadata menyertakan informasi tentang apakah objek berisi teks bergaya dan tentang jenis teks dalam objek. Ketika Anda meletakkan klip pada {i>clipboard<i}, informasi ini tersedia untuk menempelkan aplikasi, yang dapat memeriksa apakah mereka dapat menangani data klip.

Objek ClipData.Item berisi data teks, URI, atau intent:

Teks
CharSequence.
URI
Uri. Ini biasanya berisi URI penyedia konten, meskipun setiap URI diizinkan. Aplikasi yang menyediakan data menempatkan URI pada papan klip. Lamaran yang ingin menempelkan data, mendapatkan URI dari papan klip dan menggunakannya untuk mengakses konten penyedia layanan atau sumber data lainnya dan mengambil datanya.
Intent
Intent. Jenis data ini memungkinkan Anda menyalin pintasan aplikasi ke papan klip. Pengguna kemudian dapat menempelkan pintasan ke dalam aplikasi mereka untuk digunakan nanti.

Anda dapat menambahkan lebih dari satu objek ClipData.Item ke klip. Hal ini memungkinkan pengguna menyalin dan menempelkan beberapa pilihan sebagai satu klip. Misalnya, jika Anda memiliki widget daftar yang memungkinkan pengguna memilih lebih dari satu item sekaligus, Anda dapat menyalin semua item ke {i>clipboard<i} sekaligus. Yang akan dilakukan ini, buat ClipData.Item terpisah untuk setiap item daftar, lalu tambahkan ClipData.Item ke objek ClipData.

Metode kemudahan ClipData

Class ClipData menyediakan metode kemudahan statis untuk membuat Objek ClipData dengan satu objek ClipData.Item dan Objek ClipDescription:

newPlainText(label, text)
Menampilkan objek ClipData yang objek ClipData.Item tunggalnya berisi {i>string<i} teks. Label objek ClipDescription disetel ke label. Jenis MIME tunggal dalam ClipDescription adalah MIMETYPE_TEXT_PLAIN.

Gunakan newPlainText() untuk membuat klip dari string teks.

newUri(resolver, label, URI)
Menampilkan objek ClipData yang objek ClipData.Item tunggalnya berisi URI. Label objek ClipDescription disetel ke label. Jika URI adalah URI konten—yaitu, jika Uri.getScheme() menampilkan content:—metode ini menggunakan ContentResolver yang disediakan dalam resolver untuk mengambil jenis MIME yang tersedia dari penyedia konten lainnya. Kemudian, disimpan di ClipDescription. Untuk URI yang tidak URI content:, metode ini akan menetapkan jenis MIME ke MIMETYPE_TEXT_URILIST.

Gunakan newUri() untuk membuat klip dari URI, khususnya URI content:.

newIntent(label, intent)
Menampilkan objek ClipData yang objek ClipData.Item tunggalnya berisi Intent. Label objek ClipDescription disetel ke label. Jenis MIME disetel ke MIMETYPE_TEXT_INTENT.

Gunakan newIntent() untuk membuat klip dari objek Intent.

Mengonversi data papan klip ke teks

Meskipun aplikasi hanya menangani teks, Anda dapat menyalin data non-teks dari papan klip dengan mengonversinya dengan ClipData.Item.coerceToText() .

Metode ini mengonversi data dalam ClipData.Item menjadi teks dan menampilkan CharSequence. Nilai yang ditampilkan ClipData.Item.coerceToText() didasarkan dalam bentuk data di ClipData.Item:

Teks
Jika ClipData.Item adalah teks—yaitu, jika getText() bukan null—coerceToText() akan menampilkan teks.
URI
Jika ClipData.Item adalah URI—yaitu, jika getUri() bukan null—coerceToText() mencoba menggunakannya sebagai URI konten.
  • Jika URI adalah URI materi dan penyedia bisa mengembalikan aliran teks, coerceToText() menampilkan aliran teks.
  • Jika URI adalah URI materi tetapi penyedia tidak menawarkan aliran teks, coerceToText() menampilkan representasi URI. Representasinya adalah sama dengan yang ditampilkan oleh Uri.toString().
  • Jika URI bukan URI konten, coerceToText() akan menampilkan representasi dari URI. Representasinya sama dengan yang dikembalikan oleh Uri.toString().
Intent
Jika ClipData.Item adalah Intent—artinya, jika getIntent() bukan null—coerceToText() mengonversinya menjadi URI Intent dan menampilkannya. Representasinya sama dengan yang dikembalikan oleh Intent.toUri(URI_INTENT_SCHEME).

Framework papan klip diringkas dalam gambar 2. Untuk menyalin data, aplikasi menempatkan Objek ClipData di papan klip global ClipboardManager. Tujuan ClipData berisi satu atau beberapa objek ClipData.Item dan satu Objek ClipDescription. Untuk menempelkan data, aplikasi akan mendapatkan ClipData, mendapatkan jenis MIME dari ClipDescription, dan mengambil data dari ClipData.Item atau dari penyedia konten yang dirujuk oleh ClipData.Item.

Gambar yang menunjukkan diagram blok framework salin dan tempel
Gambar 2. Framework papan klip Android.

Salin ke papan klip

Untuk menyalin data ke papan klip, dapatkan handle untuk objek ClipboardManager global, buat objek ClipData, lalu tambahkan ClipDescription dan satu atau beberapa ClipData.Item pada objek tersebut. Kemudian, tambahkan objek ClipData yang sudah selesai ke Objek ClipboardManager. Hal ini dijelaskan lebih lanjut dalam prosedur berikut:

  1. Jika Anda menyalin data menggunakan URI konten, siapkan penyedia konten.
  2. Dapatkan papan klip sistem:

    Kotlin

    when(menuItem.itemId) {
        ...
        R.id.menu_copy -> { // if the user selects copy
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        }
    }
    

    Java

    ...
    // If the user selects copy.
    case R.id.menu_copy:
    
    // Gets a handle to the clipboard service.
    ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
  3. Salin data ke objek ClipData baru:

    • Untuk teks

      Kotlin

      // Creates a new text clip to put on the clipboard.
      val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
      

      Java

      // Creates a new text clip to put on the clipboard.
      ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
      
    • Untuk URI

      Cuplikan ini membentuk URI dengan mengenkode ID kumpulan data ke URI konten untuk penyedia. Teknik ini dibahas lebih detail dalam Mengenkode ID di URI.

      Kotlin

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      const val CONTACTS = "content://com.example.contacts"
      
      // Declares a path string for URIs, used to copy data.
      const val COPY_PATH = "/copy"
      
      // Declares the Uri to paste to the clipboard.
      val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName")
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
      

      Java

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      private static final String CONTACTS = "content://com.example.contacts";
      
      // Declares a path string for URIs, used to copy data.
      private static final String COPY_PATH = "/copy";
      
      // Declares the Uri to paste to the clipboard.
      Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
      
    • Untuk intent

      Cuplikan ini menyusun Intent untuk aplikasi, lalu menempatkan dalam objek klip:

      Kotlin

      // Creates the Intent.
      val appIntent = Intent(this, com.example.demo.myapplication::class.java)
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      val clip: ClipData = ClipData.newIntent("Intent", appIntent)
      

      Java

      // Creates the Intent.
      Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      ClipData clip = ClipData.newIntent("Intent", appIntent);
      
  4. Tempatkan objek klip baru di papan klip:

    Kotlin

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip)
    

    Java

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip);
    

Memberikan masukan saat menyalin ke papan klip

Pengguna mengharapkan masukan visual saat aplikasi menyalin konten ke papan klip. Hal ini sudah selesai otomatis untuk pengguna di Android 13 dan yang lebih tinggi, tetapi harus diterapkan secara manual di versi.

Mulai Android 13, sistem menampilkan konfirmasi visual standar saat konten ditambahkan ke papan klip. Konfirmasi baru akan melakukan hal berikut:

  • Mengonfirmasi bahwa konten berhasil disalin.
  • Memberikan pratinjau konten yang disalin.

Animasi yang menampilkan notifikasi papan klip Android 13
Gambar 3. UI yang ditampilkan saat konten memasuki papan klip di Android 13 ke atas.

Di Android 12L (API level 32) dan yang lebih rendah, pengguna mungkin tidak yakin apakah mereka berhasil menyalin atau apa yang mereka salin. Fitur ini menstandarkan berbagai notifikasi yang ditampilkan oleh aplikasi setelah menyalin dan menawarkan kontrol lebih besar atas {i>clipboard<i}.

Menghindari notifikasi duplikat

Di Android 12L (API level 32) dan yang lebih rendah, sebaiknya beri tahu pengguna saat mereka berhasil menyalin dengan memberikan masukan visual dalam aplikasi, menggunakan widget seperti Toast atau Snackbar, setelah menyalin.

Untuk menghindari tampilan informasi duplikat, sebaiknya hapus toast atau snackbar yang ditampilkan setelah salinan dalam aplikasi untuk Android 13 dan yang lebih baru.

Menampilkan snackbar setelah menyalin dalam aplikasi.
Gambar 4. Jika Anda menampilkan snackbar konfirmasi penyalinan di Android 13, pengguna melihat pesan duplikat.
Menampilkan toast setelah menyalin dalam aplikasi.
Gambar 5. Jika Anda menampilkan toast konfirmasi penyalinan di Android 13, pengguna melihat pesan duplikat.

Berikut ini contoh cara mengimplementasikannya:

fun textCopyThenPost(textCopied:String) {
    val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    // When setting the clipboard text.
    clipboardManager.setPrimaryClip(ClipData.newPlainText   ("", textCopied))
    // Only show a toast for Android 12 and lower.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2)
        Toast.makeText(context, Copied, Toast.LENGTH_SHORT).show()
}

Menambahkan konten sensitif ke papan klip

Jika aplikasi Anda mengizinkan pengguna untuk menyalin konten sensitif ke papan klip, seperti sandi atau kredit informasi kartu, Anda harus menambahkan tanda ke ClipDescription di ClipData sebelum memanggil ClipboardManager.setPrimaryClip(). Menambahkan penanda ini akan mencegah konten agar tidak muncul dalam konfirmasi visual konten yang disalin di Android 13 dan yang lebih baru.

Pratinjau teks yang disalin tanpa menambahkan flag pada konten sensitif
Gambar 6. Pratinjau teks yang disalin tanpa tanda konten sensitif.
Pratinjau teks yang disalin dengan menambahkan flag pada konten sensitif.
Gambar 7. Pratinjau teks yang disalin dengan tanda konten sensitif.

Untuk menambahkan flag pada konten sensitif, sertakan tambahan boolean ke ClipDescription. Semua aplikasi harus melakukan ini, terlepas dari API level yang ditargetkan.

// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

// If your app is compiled with a lower SDK.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean("android.content.extra.IS_SENSITIVE", true)
    }
}

Tempel dari papan klip

Seperti yang dijelaskan sebelumnya, menempelkan data dari {i>clipboard<i} dengan mendapatkan objek {i>clipboard<i} global, mendapatkan objek klip, melihat datanya, dan jika memungkinkan, menyalin data dari objek klip ke penyimpanan Anda sendiri. Bagian ini menjelaskan secara detail cara menempelkan tiga bentuk papan klip layanan otomatis dan data skalabel.

Tempelkan teks biasa

Untuk menempelkan teks biasa, dapatkan papan klip global dan verifikasi bahwa papan klip tersebut dapat menampilkan teks biasa. Lalu dapatkan objek klip dan salin teksnya ke penyimpanan Anda sendiri menggunakan getText(), seperti yang dijelaskan di prosedur berikut:

  1. Dapatkan objek ClipboardManager global menggunakan getSystemService(CLIPBOARD_SERVICE). Selain itu, deklarasikan variabel global untuk menampung teks yang ditempelkan:

    Kotlin

    var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    var pasteData: String = ""
    

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
    
  2. Tentukan apakah Anda perlu mengaktifkan atau menonaktifkan "tempel" opsi yang ada di aktivitas Anda. Pastikan bahwa papan klip berisi klip dan Anda dapat menangani jenis datanya yang diwakili oleh klip:

    Kotlin

    // Gets the ID of the "paste" menu item.
    val pasteItem: MenuItem = menu.findItem(R.id.menu_paste)
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    pasteItem.isEnabled = when {
        !clipboard.hasPrimaryClip() -> {
            false
        }
        !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> {
            // Disables the paste menu item, since the clipboard has data but it
            // isn't plain text.
            false
        }
        else -> {
            // Enables the paste menu item, since the clipboard contains plain text.
            true
        }
    }
    

    Java

    // Gets the ID of the "paste" menu item.
    MenuItem pasteItem = menu.findItem(R.id.menu_paste);
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    if (!(clipboard.hasPrimaryClip())) {
    
        pasteItem.setEnabled(false);
    
    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
    
        // Disables the paste menu item, since the clipboard has data but
        // it isn't plain text.
        pasteItem.setEnabled(false);
    } else {
    
        // Enables the paste menu item, since the clipboard contains plain text.
        pasteItem.setEnabled(true);
    }
    
  3. Salin data dari papan klip. Titik dalam kode ini hanya bisa dijangkau jika "tempelkan" diaktifkan, sehingga Anda dapat mengasumsikan bahwa papan klip berisi teks. Anda belum tahu apakah kolom ini berisi string teks atau URI yang mengarah ke teks biasa. Cuplikan kode berikut mengujinya, tetapi hanya menampilkan kode untuk menangani teks biasa:

    Kotlin

    when (menuItem.itemId) {
        ...
        R.id.menu_paste -> {    // Responds to the user selecting "paste".
            // Examines the item on the clipboard. If getText() doesn't return null,
            // the clip item contains the text. Assumes that this application can only
            // handle one item at a time.
            val item = clipboard.primaryClip.getItemAt(0)
    
            // Gets the clipboard as text.
            pasteData = item.text
    
            return if (pasteData != null) {
                // If the string contains data, then the paste operation is done.
                true
            } else {
                // The clipboard doesn't contain text. If it contains a URI,
                // attempts to get data from it.
                val pasteUri: Uri? = item.uri
    
                if (pasteUri != null) {
                    // If the URI contains something, try to get text from it.
    
                    // Calls a routine to resolve the URI and get data from it.
                    // This routine isn't presented here.
                    pasteData = resolveUri(pasteUri)
                    true
                } else {
    
                    // Something is wrong. The MIME type was plain text, but the
                    // clipboard doesn't contain text or a Uri. Report an error.
                    Log.e(TAG,"Clipboard contains an invalid data type")
                    false
                }
            }
        }
    }
    

    Java

    // Responds to the user selecting "paste".
    case R.id.menu_paste:
    
    // Examines the item on the clipboard. If getText() does not return null,
    // the clip item contains the text. Assumes that this application can only
    // handle one item at a time.
     ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
    
    // Gets the clipboard as text.
    pasteData = item.getText();
    
    // If the string contains data, then the paste operation is done.
    if (pasteData != null) {
        return true;
    
    // The clipboard doesn't contain text. If it contains a URI, attempts to get
    // data from it.
    } else {
        Uri pasteUri = item.getUri();
    
        // If the URI contains something, try to get text from it.
        if (pasteUri != null) {
    
            // Calls a routine to resolve the URI and get data from it.
            // This routine isn't presented here.
            pasteData = resolveUri(Uri);
            return true;
        } else {
    
            // Something is wrong. The MIME type is plain text, but the
            // clipboard doesn't contain text or a Uri. Report an error.
            Log.e(TAG, "Clipboard contains an invalid data type");
            return false;
        }
    }
    

Menempelkan data dari URI konten

Jika objek ClipData.Item berisi URI konten dan Anda menentukan bahwa Anda bisa menangani salah satu jenis MIME-nya, membuat ContentResolver, dan memanggil konten yang sesuai penyedia layanan untuk mengambil data.

Prosedur berikut menjelaskan cara mendapatkan data dari penyedia konten berdasarkan URI konten pada papan klip. Alat ini memeriksa apakah jenis MIME yang bisa digunakan aplikasi tersedia dari penyedia layanan.

  1. Deklarasikan variabel global untuk memuat jenis MIME:

    Kotlin

    // Declares a MIME type constant to match against the MIME types offered
    // by the provider.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
    

    Java

    // Declares a MIME type constant to match against the MIME types offered by
    // the provider.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
    
  2. Dapatkan papan klip global. Dapatkan juga resolver konten agar Anda dapat mengakses penyedia konten:

    Kotlin

    // Gets a handle to the Clipboard Manager.
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
    // Gets a content resolver instance.
    val cr = contentResolver
    

    Java

    // Gets a handle to the Clipboard Manager.
    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    
    // Gets a content resolver instance.
    ContentResolver cr = getContentResolver();
    
  3. Dapatkan klip utama dari papan klip dan dapatkan kontennya sebagai URI:

    Kotlin

    // Gets the clipboard data from the clipboard.
    val clip: ClipData? = clipboard.primaryClip
    
    clip?.run {
    
        // Gets the first item from the clipboard data.
        val item: ClipData.Item = getItemAt(0)
    
        // Tries to get the item's contents as a URI.
        val pasteUri: Uri? = item.uri
    

    Java

    // Gets the clipboard data from the clipboard.
    ClipData clip = clipboard.getPrimaryClip();
    
    if (clip != null) {
    
        // Gets the first item from the clipboard data.
        ClipData.Item item = clip.getItemAt(0);
    
        // Tries to get the item's contents as a URI.
        Uri pasteUri = item.getUri();
    
  4. Menguji apakah URI merupakan URI konten dengan memanggil getType(Uri). Metode ini akan menampilkan null jika Uri tidak mengarah ke penyedia konten yang valid.

    Kotlin

        // If the clipboard contains a URI reference...
        pasteUri?.let {
    
            // ...is this a content URI?
            val uriMimeType: String? = cr.getType(it)
    

    Java

        // If the clipboard contains a URI reference...
        if (pasteUri != null) {
    
            // ...is this a content URI?
            String uriMimeType = cr.getType(pasteUri);
    
  5. Menguji apakah penyedia konten mendukung jenis MIME yang dipahami aplikasi. Jika ya, panggil ContentResolver.query() untuk mendapatkan data. Nilai yang ditampilkan adalah Cursor.

    Kotlin

            // If the return value isn't null, the Uri is a content Uri.
            uriMimeType?.takeIf {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                it == MIME_TYPE_CONTACT
            }?.apply {
    
                // Get the data from the content provider.
                cr.query(pasteUri, null, null, null, null)?.use { pasteCursor ->
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                    }
    
                    // Kotlin `use` automatically closes the Cursor.
                }
            }
        }
    }
    

    Java

            // If the return value isn't null, the Uri is a content Uri.
            if (uriMimeType != null) {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                if (uriMimeType.equals(MIME_TYPE_CONTACT)) {
    
                    // Get the data from the content provider.
                    Cursor pasteCursor = cr.query(uri, null, null, null, null);
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor != null) {
                        if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                        }
                    }
    
                    // Close the Cursor.
                    pasteCursor.close();
                 }
             }
         }
    }
    

Menempelkan Intent

Untuk menempelkan intent, pertama-tama dapatkan papan klip global. Periksa objek ClipData.Item untuk melihat apakah file tersebut berisi Intent. Lalu, panggil getIntent() untuk menyalin ke penyimpanan Anda sendiri. Cuplikan berikut menunjukkan hal tersebut:

Kotlin

// Gets a handle to the Clipboard Manager.
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

// Checks whether the clip item contains an Intent by testing whether
// getIntent() returns null.
val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Java

// Gets a handle to the Clipboard Manager.
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks whether the clip item contains an Intent, by testing whether
// getIntent() returns null.
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Notifikasi sistem ditampilkan saat aplikasi Anda mengakses data papan klip

Di Android 12 (API level 31) dan yang lebih baru, sistem biasanya menampilkan pesan toast saat aplikasi panggilan telepon getPrimaryClip() Teks di dalam pesan toast berisi format berikut:

APP pasted from your clipboard

Sistem tidak menampilkan pesan toast saat aplikasi Anda melakukan salah satu hal berikut:

  • Akses ClipData dari aplikasi Anda sendiri.
  • Berulang kali mengakses ClipData dari aplikasi tertentu. Toast hanya muncul saat aplikasi Anda mengakses data dari aplikasi tersebut untuk pertama kalinya.
  • Mengambil metadata untuk objek klip, seperti dengan memanggil getPrimaryClipDescription() bukan getPrimaryClip().

Menggunakan penyedia konten untuk menyalin data yang kompleks

Penyedia konten mendukung penyalinan data kompleks seperti data database atau aliran file. Untuk menyalin data, letakkan URI konten di papan klip. Tempelkan aplikasi lalu dapatkan URI ini dari {i>clipboard<i} dan menggunakannya untuk mengambil data {i>database<i} atau deskriptor aliran file.

Karena aplikasi penempelan hanya memiliki URI konten untuk data Anda, aplikasi perlu mengetahui data yang akan diambil. Anda dapat memberikan informasi ini dengan mengenkode ID untuk data URI itu sendiri, atau Anda dapat memberikan URI unik yang menampilkan data yang ingin Anda salin. Yang mana yang Anda pilih tergantung pada organisasi data.

Bagian berikut menjelaskan cara menyiapkan URI, menyediakan data kompleks, dan menyediakan feed. Deskripsinya mengasumsikan Anda sudah familier dengan prinsip umum penyedia konten desain.

Mengenkode ID di URI

Teknik yang berguna untuk menyalin data ke {i>clipboard<i} dengan URI adalah data pada URI itu sendiri. Penyedia konten Anda kemudian bisa mendapatkan ID dari URI dan menggunakan untuk mengambil data. Aplikasi penempelan tidak harus mengetahui bahwa pengenal ada. Ini hanya perlu mendapatkan "referensi" Anda—URI dan pengenal—dari papan klip, memberikannya ke penyedia konten, dan mendapatkan kembali datanya.

Anda biasanya mengenkode ID ke URI konten dengan menyambungkannya ke bagian akhir URI. Misalnya, anggaplah Anda mendefinisikan URI penyedia Anda sebagai string berikut:

"content://com.example.contacts"

Jika Anda ingin mengenkode nama ke URI ini, gunakan cuplikan kode berikut:

Kotlin

val uriString = "content://com.example.contacts/Smith"

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
val copyUri = Uri.parse(uriString)

Java

String uriString = "content://com.example.contacts" + "/" + "Smith";

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
Uri copyUri = Uri.parse(uriString);

Jika sudah menggunakan penyedia konten, Anda mungkin ingin menambahkan jalur URI baru yang menunjukkan URI digunakan untuk menyalin. Misalnya, anggaplah Anda sudah memiliki jalur URI berikut:

"content://com.example.contacts/people"
"content://com.example.contacts/people/detail"
"content://com.example.contacts/people/images"

Anda dapat menambahkan jalur lain yang digunakan untuk menyalin URI:

"content://com.example.contacts/copying"

Anda kemudian dapat mendeteksi "salinan" URI dengan pencocokan pola dan tangani dengan kode yang khusus untuk menyalin dan menempel.

Anda biasanya menggunakan teknik encoding jika sudah menggunakan penyedia konten, {i>database<i}, atau tabel internal untuk mengatur data Anda. Dalam kasus ini, Anda memiliki beberapa bagian data yang ingin Anda salin, dan mungkin ID unik untuk setiap bagian. Sebagai respons atas kueri dari menempelkan aplikasi, Anda dapat mencari data berdasarkan pengenalnya dan mengembalikannya.

Jika tidak memiliki beberapa data, Anda mungkin tidak perlu mengenkode ID. Anda dapat menggunakan URI yang unik untuk penyedia Anda. Sebagai respons terhadap kueri, penyedia Anda akan menampilkan data yang ada di dalamnya.

Menyalin struktur data

Siapkan penyedia konten untuk menyalin dan menempelkan data kompleks sebagai subclass dari ContentProvider komponen. Enkode URI yang Anda tempatkan pada papan klip sehingga mengarah ke catatan yang tepat yang ingin Anda sediakan. Selain itu, pertimbangkan status aplikasi yang ada:

  • Jika sudah memiliki penyedia konten, Anda dapat menambahkan fungsinya. Anda mungkin hanya perlu memodifikasi metode query()-nya untuk menangani URI yang berasal dari aplikasi yang menempelkan data. Anda mungkin ingin memodifikasi metode untuk menangani “salinan” URI pola.
  • Jika aplikasi Anda memiliki database internal, Anda mungkin ingin memindahkan database ini ke penyedia konten untuk memudahkan penyalinannya.
  • Jika tidak menggunakan database, Anda bisa mengimplementasikan penyedia konten sederhana yang tujuan utamanya adalah menawarkan data ke aplikasi yang ditempelkan dari {i>clipboard<i}.

Di penyedia konten, ganti setidaknya metode berikut:

query()
Menempelkan aplikasi beranggapan bahwa aplikasi dapat memperoleh data Anda dengan menggunakan metode ini dengan URI yang Anda ke {i>clipboard<i}. Untuk mendukung penyalinan, buat metode ini mendeteksi URI yang berisi "salin" . Aplikasi Anda kemudian dapat membuat "salinan" URI yang akan dimasukkan {i>clipboard<i}, yang berisi jalur penyalinan dan penunjuk ke catatan yang tepat yang ingin Anda salin.
getType()
Metode ini harus menampilkan jenis MIME untuk data yang ingin Anda salin. Metode newUri() memanggil getType() untuk memasukkan jenis MIME ke dalam ClipData yang baru .

Jenis MIME untuk data kompleks dijelaskan dalam Penyedia konten.

Anda tidak perlu memiliki salah satu metode penyedia konten lain, seperti insert() atau update(). Aplikasi penempelan hanya perlu mendapatkan jenis MIME yang didukung dan menyalin data dari penyedia Anda. Jika Anda sudah memilikinya, metode tersebut tidak akan mengganggu operasi penyalinan.

Cuplikan berikut menunjukkan cara menyiapkan aplikasi Anda untuk menyalin data kompleks:

  1. Dalam konstanta global untuk aplikasi Anda, deklarasikan string URI dasar dan jalur yang mengidentifikasi string URI yang Anda gunakan untuk menyalin data. Juga deklarasikan jenis MIME untuk elemen yang disalin layanan otomatis dan data skalabel.

    Kotlin

    // Declares the base URI string.
    private const val CONTACTS = "content://com.example.contacts"
    
    // Declares a path string for URIs that you use to copy data.
    private const val COPY_PATH = "/copy"
    
    // Declares a MIME type for the copied data.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
    

    Java

    // Declares the base URI string.
    private static final String CONTACTS = "content://com.example.contacts";
    
    // Declares a path string for URIs that you use to copy data.
    private static final String COPY_PATH = "/copy";
    
    // Declares a MIME type for the copied data.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
    
  2. Pada aktivitas tempat pengguna menyalin data, siapkan kode untuk menyalin data ke papan klip. Sebagai respons terhadap permintaan penyalinan, tempatkan URI di papan klip.

    Kotlin

    class MyCopyActivity : Activity() {
        ...
    when(item.itemId) {
        R.id.menu_copy -> { // The user has selected a name and is requesting a copy.
            // Appends the last name to the base URI.
            // The name is stored in "lastName".
            uriString = "$CONTACTS$COPY_PATH/$lastName"
    
            // Parses the string into a URI.
            val copyUri: Uri? = Uri.parse(uriString)
    
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
            val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
    
            // Sets the clipboard's primary clip.
            clipboard.setPrimaryClip(clip)
        }
    }
    

    Java

    public class MyCopyActivity extends Activity {
        ...
    // The user has selected a name and is requesting a copy.
    case R.id.menu_copy:
    
        // Appends the last name to the base URI.
        // The name is stored in "lastName".
        uriString = CONTACTS + COPY_PATH + "/" + lastName;
    
        // Parses the string into a URI.
        Uri copyUri = Uri.parse(uriString);
    
        // Gets a handle to the clipboard service.
        ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    
        // Sets the clipboard's primary clip.
        clipboard.setPrimaryClip(clip);
    
  3. Dalam cakupan global penyedia konten Anda, buat pencocok URI dan tambahkan pola URI yang cocok dengan URI yang Anda tempatkan di papan klip.

    Kotlin

    // A Uri Match object that simplifies matching content URIs to patterns.
    private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    
        // Adds a matcher for the content URI. It matches.
        // "content://com.example.contacts/copy/*"
        addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT)
    }
    
    // An integer to use in switching based on the incoming URI pattern.
    private const val GET_SINGLE_CONTACT = 0
    ...
    class MyCopyProvider : ContentProvider() {
        ...
    }
    

    Java

    public class MyCopyProvider extends ContentProvider {
        ...
    // A Uri Match object that simplifies matching content URIs to patterns.
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
    // An integer to use in switching based on the incoming URI pattern.
    private static final int GET_SINGLE_CONTACT = 0;
    ...
    // Adds a matcher for the content URI. It matches
    // "content://com.example.contacts/copy/*"
    sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
    
  4. Siapkan query() . Metode ini dapat menangani pola URI yang berbeda, tergantung pada cara Anda mengodekannya, tetapi hanya pola untuk operasi penyalinan {i> clipboard<i}.

    Kotlin

    // Sets up your provider's query() method.
    override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        ...
        // When based on the incoming content URI:
        when(sUriMatcher.match(uri)) {
    
            GET_SINGLE_CONTACT -> {
    
                // Queries and returns the contact for the requested name. Decodes
                // the incoming URI, queries the data model based on the last name,
                // and returns the result as a Cursor.
            }
        }
        ...
    }
    

    Java

    // Sets up your provider's query() method.
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
        ...
        // Switch based on the incoming content URI.
        switch (sUriMatcher.match(uri)) {
    
        case GET_SINGLE_CONTACT:
    
            // Queries and returns the contact for the requested name. Decodes the
            // incoming URI, queries the data model based on the last name, and
            // returns the result as a Cursor.
        ...
    }
    
  5. Siapkan metode getType() untuk menampilkan jenis MIME yang sesuai untuk disalin data:

    Kotlin

    // Sets up your provider's getType() method.
    override fun getType(uri: Uri): String? {
        ...
        return when(sUriMatcher.match(uri)) {
            GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT
            ...
        }
    }
    

    Java

    // Sets up your provider's getType() method.
    public String getType(Uri uri) {
        ...
        switch (sUriMatcher.match(uri)) {
        case GET_SINGLE_CONTACT:
            return (MIME_TYPE_CONTACT);
        ...
        }
    }
    

Bagian Menempelkan data dari URI konten menjelaskan cara mendapatkan URI konten dari papan klip dan menggunakannya untuk mendapatkan dan menempelkan data.

Salin aliran data

Anda dapat menyalin dan menempelkan teks dan data biner dalam jumlah besar sebagai aliran. Data dapat memiliki formulir seperti berikut:

  • File yang disimpan di perangkat sebenarnya
  • Streaming dari soket
  • Data dalam jumlah besar yang disimpan dalam sistem database yang mendasari penyedia

Penyedia konten untuk aliran data menyediakan akses ke datanya dengan objek deskriptor file, seperti AssetFileDescriptor, bukan objek Cursor. Aplikasi penempelan membaca aliran data menggunakan deskriptor file.

Untuk menyiapkan aplikasi Anda agar dapat menyalin aliran data dengan penyedia, ikuti langkah-langkah berikut:

  1. Siapkan URI konten untuk aliran data yang Anda tempatkan di papan klip. Opsi untuk melakukan langkah ini mencakup hal berikut:
    • Lakukan enkode ID untuk aliran data ke URI, seperti yang dijelaskan dalam Enkode ID di bagian URI, lalu pertahankan dalam penyedia yang berisi ID dan nama aliran data yang sesuai.
    • Lakukan enkode nama aliran data langsung di URI.
    • Gunakan URI unik yang selalu menampilkan aliran saat ini dari penyedia. Jika Anda gunakan opsi ini, ingatlah untuk memperbarui penyedia agar mengarah ke aliran yang berbeda kapan pun Anda menyalin streaming ke papan klip menggunakan URI.
  2. Berikan jenis MIME untuk setiap jenis aliran data yang akan Anda tawarkan. Aplikasi penempelan memerlukan informasi ini untuk menentukan apakah mereka dapat menempelkan data pada {i>clipboard<i}.
  3. Implementasikan salah satu metode ContentProvider yang menampilkan deskriptor file untuk melakukan streaming. Jika Anda mengenkode ID pada URI konten, gunakan metode ini untuk menentukan streaming untuk membuka.
  4. Untuk menyalin aliran data ke papan klip, buat URI konten dan tempatkan pada papan klip.

Untuk menempelkan aliran data, aplikasi mendapatkan klip dari papan klip, mendapatkan URI, dan menggunakan dalam panggilan ke metode deskriptor file ContentResolver yang membuka aliran data. Tujuan Metode ContentResolver memanggil metode ContentProvider yang sesuai, dengan meneruskan URI konten. Penyedia Anda mengembalikan deskriptor file ke Metode ContentResolver. Aplikasi penempelan kemudian bertanggung jawab untuk membaca data dari aliran data.

Daftar berikut menunjukkan metode deskriptor file yang paling penting untuk penyedia konten. Masing-masing nya memiliki metode ContentResolver yang sesuai dengan string "Deskripsi" ditambahkan ke nama metode. Misalnya, ContentResolver analog dari openAssetFile() bernilai openAssetFileDescriptor().

openTypedAssetFile()

Metode ini mengembalikan deskriptor file aset, namun hanya jika jenis MIME yang diberikan didukung oleh penyedia. Pemanggil—aplikasi yang melakukan penempelan—menyediakan pola jenis MIME. Penyedia konten aplikasi yang menyalin URI ke papan klip akan menampilkan handle file AssetFileDescriptor jika dapat memberikan Jenis MIME dan menampilkan pengecualian jika tidak bisa.

Metode ini menangani subbagian file. Anda dapat menggunakannya untuk membaca aset yang penyedia konten telah disalin ke papan klip.

openAssetFile()
Metode ini adalah bentuk yang lebih umum dari openTypedAssetFile(). Tidak ada filter untuk jenis MIME yang diperbolehkan, tetapi bisa membaca subbagian file.
openFile()
Ini adalah bentuk yang lebih umum dari openAssetFile(). AD tidak dapat membaca subbagian dari .

Jika ingin, Anda dapat menggunakan openPipeHelper() dengan metode deskriptor file Anda. Hal ini memungkinkan aplikasi penempelan membaca data streaming dalam latar belakang menggunakan pipe. Untuk menggunakan metode ini, implementasikan atribut ContentProvider.PipeDataWriter dalam antarmuka berbasis web yang sederhana.

Merancang fungsi {i>copy<i} dan {i>paste<i} yang efektif

Untuk mendesain fungsi salin dan tempel yang efektif untuk aplikasi Anda, ingatlah hal-hal berikut:

  • Kapan pun, hanya akan ada satu klip di papan klip. Operasi penyalinan baru oleh dalam sistem akan menimpa klip sebelumnya. Karena pengguna mungkin menavigasi dari aplikasi dan menyalin sebelum kembali, Anda tidak dapat mengasumsikan papan klip berisi klip yang sebelumnya disalin pengguna di aplikasi Anda.
  • Tujuan yang dimaksud dari beberapa objek ClipData.Item per klip adalah untuk mendukung penyalinan dan penempelan dari banyak pilihan daripada berbagai bentuk referensi ke satu pilihan. Anda biasanya menginginkan semua ClipData.Item objek dalam klip agar memiliki bentuk yang sama. Artinya, semuanya harus berupa teks sederhana, konten, URI, atau Intent, dan tidak dicampur.
  • Saat memberikan data, Anda dapat menawarkan representasi MIME yang berbeda. Menambahkan jenis MIME yang Anda dukung ke ClipDescription, lalu menerapkan jenis MIME dalam penyedia konten Anda.
  • Saat Anda mendapatkan data dari papan klip, aplikasi Anda bertanggung jawab untuk memeriksa jenis MIME yang tersedia dan kemudian memutuskan data mana, jika ada, yang akan digunakan. Bahkan jika ada pada papan klip dan pengguna meminta penempelan, aplikasi Anda tidak diperlukan untuk menempelkannya. Lakukan penempelan jika jenis MIME kompatibel. Anda mungkin memaksa data di papan klip menjadi teks menggunakan coerceToText(). Jika aplikasi Anda mendukung lebih dari satu jenis MIME yang tersedia, Anda bisa mengizinkan pengguna memilih mana yang akan digunakan.