Copia e incolla

Android offre un potente framework basato sugli appunti per copiare e incollare. Supporta tipi di dati semplici e complessi, tra cui stringhe di testo, strutture di dati complesse, dati di flussi di testo e binari e asset per applicazioni. I dati di testo semplici vengono memorizzati direttamente negli appunti, mentre i dati complessi vengono archiviati come riferimento che l'applicazione incollando viene risolta con un fornitore di contenuti. Le operazioni di copia e incolla funzionano sia all'interno di un'applicazione sia tra applicazioni che implementano il framework.

Poiché parte del framework utilizza fornitori di contenuti, il presente documento presuppone una certa familiarità con l'API Android Content Provider, descritta nella sezione Fornitori di contenuti.

Gli utenti si aspettano un feedback quando copiano i contenuti negli appunti, quindi oltre al framework che consente il copia e incolla, Android mostra agli utenti una UI predefinita durante la copia in Android 13 (livello API 33) e versioni successive. A causa di questa funzionalità, esiste il rischio di una notifica duplicata. Per ulteriori informazioni su questo caso limite, consulta la sezione Evitare notifiche duplicate.

Un'animazione che mostra la notifica relativa agli appunti di Android 13
Figura 1. UI mostrata quando i contenuti vengono inseriti negli appunti in Android 13 e versioni successive.

Fornisci feedback manualmente agli utenti durante la copia in Android 12L (livello API 32) e versioni precedenti. Leggi i consigli in merito in questo documento.

La struttura degli appunti

Quando utilizzi il framework degli appunti, inserisci i dati in un oggetto clip e poi inserisci l'oggetto clip negli appunti a livello di sistema. L'oggetto clip può avere tre forme:

Testo
Una stringa di testo. Inserisci la stringa direttamente nell'oggetto clip, che quindi inserisci negli appunti. Per incollare la stringa, recupera l'oggetto clip dagli appunti e copiala nello spazio di archiviazione dell'applicazione.
URI
Un oggetto Uri che rappresenta qualsiasi tipo di URI. Ciò avviene principalmente per copiare dati complessi da un fornitore di contenuti. Per copiare i dati, inserisci un oggetto Uri in un oggetto clip e inserisci l'oggetto clip negli appunti. Per incollare i dati, recupera l'oggetto clip, recupera l'oggetto Uri, risolvilo in un'origine dati, ad esempio un fornitore di contenuti, e copia i dati dall'origine nello spazio di archiviazione dell'applicazione.
Intenzione
Un Intent. Questo supporta la copia delle scorciatoie delle applicazioni. Per copiare i dati, crea un elemento Intent, inseriscilo in un oggetto clip e inserisci l'oggetto clip negli appunti. Per incollare i dati, recupera l'oggetto clip, quindi copia l'oggetto Intent nell'area di memoria dell'applicazione.

Gli appunti contengono un solo oggetto clip alla volta. Quando un'applicazione inserisce un oggetto clip negli appunti, l'oggetto clip precedente scompare.

Se vuoi consentire agli utenti di incollare dati nella tua applicazione, non devi gestire tutti i tipi di dati. Puoi esaminare i dati negli appunti prima di offrire agli utenti l'opzione per incollarli. Oltre ad avere un determinato modulo dati, l'oggetto clip contiene anche metadati che indicano quali tipi MIME sono disponibili. Questi metadati ti aiutano a decidere se la tua applicazione può fare qualcosa di utile con i dati degli appunti. Ad esempio, se disponi di un'applicazione che gestisce principalmente il testo, ti consigliamo di ignorare gli oggetti clip che contengono un URI o un intent.

Potresti anche consentire agli utenti di incollare il testo a prescindere dalla forma dei dati negli appunti. Per farlo, forza i dati degli appunti in una rappresentazione di testo, quindi incolla questo testo. Questa operazione è descritta nella sezione Convertire gli appunti in testo.

Lezioni per appunti

Questa sezione descrive i corsi utilizzati dal framework degli appunti.

Gestione appunti

Gli appunti del sistema Android sono rappresentati dalla classe ClipboardManager globale. Non creare un'istanza direttamente per questo corso. Per ottenere un riferimento, richiama getSystemService(CLIPBOARD_SERVICE).

ClipData, ClipData.Item e ClipDescription

Per aggiungere dati agli appunti, crea un oggetto ClipData che contenga una descrizione dei dati e dei dati stessi. Gli appunti contengono un ClipData alla volta. Un elemento ClipData contiene un oggetto ClipDescription e uno o più oggetti ClipData.Item.

Un oggetto ClipDescription contiene metadati relativi al clip. In particolare, contiene un array di tipi MIME disponibili per i dati del clip. Inoltre, su Android 12 (livello API 31) e versioni successive, i metadati includono informazioni che indicano se l'oggetto contiene testo stilizzato e sul tipo di testo nell'oggetto. Quando inserisci un clip negli appunti, queste informazioni sono disponibili per incollare le applicazioni, che consentono di verificare se sono in grado di gestire i dati del clip.

Un oggetto ClipData.Item contiene il testo, l'URI o i dati di intent:

Testo
Un CharSequence.
URI
Un Uri. In genere contiene un URI del fornitore di contenuti, anche se è consentito qualsiasi URI. L'applicazione che fornisce i dati inserisce l'URI negli appunti. Le applicazioni che vogliono incollare i dati recuperano l'URI dagli appunti e lo utilizzano per accedere al fornitore di contenuti o a un'altra origine dati e recuperare i dati.
Intenzione
Un Intent. Questo tipo di dati consente di copiare una scorciatoia dell'applicazione negli appunti. Gli utenti potranno quindi incollare la scorciatoia nelle proprie applicazioni per utilizzarla in un secondo momento.

Puoi aggiungere più di un oggetto ClipData.Item a un clip. Ciò consente agli utenti di copiare e incollare più selezioni come un singolo clip. Ad esempio, se disponi di un widget elenco che consente all'utente di selezionare più elementi alla volta, puoi copiare tutti gli elementi negli appunti contemporaneamente. A questo scopo, crea un elemento ClipData.Item separato per ogni elemento dell'elenco, quindi aggiungi gli oggetti ClipData.Item all'oggetto ClipData.

Metodi di praticità di ClipData

La classe ClipData fornisce metodi statici per creare un oggetto ClipData con un singolo oggetto ClipData.Item e un semplice oggetto ClipDescription:

newPlainText(label, text)
Restituisci un oggetto ClipData il cui singolo oggetto ClipData.Item contiene una stringa di testo. L'etichetta dell'oggetto ClipDescription è impostata su label. L'unico tipo MIME in ClipDescription è MIMETYPE_TEXT_PLAIN.

Usa newPlainText() per creare un clip da una stringa di testo.

newUri(resolver, label, URI)
Restituisci un oggetto ClipData il cui singolo oggetto ClipData.Item contiene un URI. L'etichetta dell'oggetto ClipDescription è impostata su label. Se l'URI è un URI dei contenuti, ovvero se Uri.getScheme() restituisce content:, il metodo utilizza l'oggetto ContentResolver fornito in resolver per recuperare i tipi MIME disponibili dal fornitore di contenuti. Poi le archivia in ClipDescription. Per un URI che non sia un URI content:, il metodo imposta il tipo MIME su MIMETYPE_TEXT_URILIST.

Usa newUri() per creare un clip da un URI, in particolare un URI content:.

newIntent(label, intent)
Restituisci un oggetto ClipData il cui singolo oggetto ClipData.Item contiene un Intent. L'etichetta dell'oggetto ClipDescription è impostata su label. Il tipo MIME è impostato su MIMETYPE_TEXT_INTENT.

Usa newIntent() per creare un clip da un oggetto Intent.

Converti i dati degli appunti in testo

Anche se l'applicazione gestisce solo il testo, puoi copiare dagli appunti i dati non testuali convertindoli con il metodo ClipData.Item.coerceToText().

Questo metodo converte i dati in ClipData.Item in testo e restituisce un CharSequence. Il valore restituito da ClipData.Item.coerceToText() si basa sulla forma dei dati in ClipData.Item:

Testo
Se ClipData.Item è un testo, ovvero se getText() non è nullo, CoerceToText() restituisce il testo.
URI
Se ClipData.Item è un URI, ovvero se getUri() non è nullo, coerceToText() tenta di utilizzarlo come URI dei contenuti.
  • Se l'URI è un URI di contenuti e il provider può restituire uno stream di testo, coerceToText() restituisce uno stream di testo.
  • Se l'URI è un URI di contenuti, ma il provider non offre uno stream di testo, coerceToText() restituisce una rappresentazione dell'URI. La rappresentazione è la stessa restituita da Uri.toString().
  • Se l'URI non è un URI dei contenuti, coerceToText() restituisce una rappresentazione dell'URI. La rappresentazione è la stessa di quella restituita da Uri.toString().
Intenzione
Se ClipData.Item è un Intent, ovvero se getIntent() non è null, coerceToText() lo converte in un URI di intent e lo restituisce. La rappresentazione è la stessa di quella restituita da Intent.toUri(URI_INTENT_SCHEME).

Il framework degli appunti è riassunto nella figura 2. Per copiare i dati, un'applicazione inserisce un oggetto ClipData negli appunti globali di ClipboardManager. ClipData contiene uno o più oggetti ClipData.Item e un oggetto ClipDescription. Per incollare i dati, un'applicazione riceve ClipData, recupera il tipo MIME da ClipDescription e recupera i dati da ClipData.Item o dal fornitore di contenuti a cui fa riferimento ClipData.Item.

Un'immagine che mostra un diagramma a blocchi del framework di copia e incolla
Figura 2. Il framework degli appunti di Android.

Copia negli appunti

Per copiare i dati negli appunti, ottieni un handle nell'oggetto ClipboardManager globale, crea un oggetto ClipData e aggiungi all'oggetto ClipDescription e uno o più ClipData.Item oggetti. Quindi, aggiungi l'oggetto ClipData completato all'oggetto ClipboardManager. Questa procedura viene descritta in modo più approfondito nella procedura seguente:

  1. Se copi i dati utilizzando un URI dei contenuti, configura un fornitore di contenuti.
  2. Scarica gli appunti di sistema:

    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. Copia i dati in un nuovo oggetto ClipData:

    • Per il testo

      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!");
      
    • Per un URI

      Questo snippet costruisce un URI codificando un ID record nell'URI dei contenuti per il provider. Questa tecnica è trattata più in dettaglio nella sezione Codifica di un identificatore sull'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);
      
    • Per intenzione

      Questo snippet crea un Intent per un'applicazione e quindi lo inserisce nell'oggetto clip:

      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. Inserisci il nuovo oggetto clip negli appunti:

    Kotlin

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

    Java

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

Fornisci un feedback durante la copia negli appunti

Gli utenti si aspettano un feedback visivo quando un'app copia i contenuti negli appunti. Questa operazione viene eseguita automaticamente per gli utenti con Android 13 e versioni successive, ma deve essere implementata manualmente nelle versioni precedenti.

A partire da Android 13, il sistema mostra una conferma visiva standard quando vengono aggiunti contenuti agli appunti. La nuova conferma comporta quanto segue:

  • Conferma che i contenuti sono stati copiati correttamente.
  • Fornisce un'anteprima dei contenuti copiati.

Un'animazione che mostra la notifica relativa agli appunti di Android 13
Figura 3. UI mostrata quando i contenuti vengono inseriti negli appunti in Android 13 e versioni successive.

In Android 12L (livello API 32) e versioni precedenti, gli utenti potrebbero non essere sicuri di aver copiato i contenuti correttamente o di ciò che hanno copiato. Questa funzionalità standardizza le varie notifiche mostrate dalle app dopo le copie e offre agli utenti un maggiore controllo sugli appunti.

Evita le notifiche duplicate

In Android 12L (livello API 32) e versioni precedenti, consigliamo di avvisare gli utenti quando eseguono correttamente la copia inviando un feedback visivo in-app tramite un widget come Toast o Snackbar, dopo la copia.

Per evitare visualizzazioni duplicate delle informazioni, ti consigliamo vivamente di rimuovere i toast o gli snackbar mostrati dopo una copia in-app per Android 13 e versioni successive.

Pubblica snackbar dopo una copia in-app.
Figura 4. Se mostri una snackbar di conferma della copia in Android 13, l'utente vedrà messaggi duplicati.
Pubblicare toast dopo una copia in-app.
Figura 5. Se mostri un avviso di conferma della copia in Android 13, l'utente vede messaggi duplicati.

Ecco un esempio di come implementare questa funzionalità:

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

Aggiungi contenuti sensibili agli appunti

Se l'app consente agli utenti di copiare contenuti sensibili negli appunti, come password o dati della carta di credito, devi aggiungere un flag a ClipDescription in ClipData prima di chiamare ClipboardManager.setPrimaryClip(). L'aggiunta di questo flag impedisce la visualizzazione di contenuti sensibili nella conferma visiva dei contenuti copiati in Android 13 e versioni successive.

Anteprima del testo copiata senza segnalare i contenuti sensibili
Figura 6. Anteprima del testo copiata senza un flag di contenuti sensibili.
Anteprima del testo copiato che segnala contenuti sensibili.
Figura 7. Anteprima del testo copiata con un flag di contenuti sensibili.

Per segnalare contenuti sensibili, aggiungi un extra booleano alla ClipDescription. Tutte le app devono eseguire questa operazione, indipendentemente dal livello API target.

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

Incolla dagli appunti

Come descritto in precedenza, incolla i dati dagli appunti recuperando l'oggetto Appunti globale, recuperando l'oggetto clip, osservandone i dati e, se possibile, copiando i dati dall'oggetto clip nel tuo spazio di archiviazione. Questa sezione spiega in dettaglio come incollare i tre tipi di dati relativi agli appunti.

Incolla testo normale

Per incollare testo normale, scarica gli appunti globali e verifica che sia in grado di restituire testo normale. Quindi recupera l'oggetto clip e copiane il testo nel tuo spazio di archiviazione utilizzando getText(), come descritto nella procedura seguente:

  1. Recupera l'oggetto globale ClipboardManager utilizzando getSystemService(CLIPBOARD_SERVICE). Inoltre, dichiara che una variabile globale contenga il testo incollato:

    Kotlin

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

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
    
  2. Determina se è necessario abilitare o disabilitare l'opzione "Incolla" nell'attività corrente. Verifica che gli appunti contengano un clip e che sia possibile gestire il tipo di dati rappresentati dal clip:

    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. Copia i dati dagli appunti. Questo punto del codice è raggiungibile solo se la voce di menu "Incolla" è abilitata, quindi supponiamo che gli appunti contengano testo normale. Non sai ancora se contiene una stringa di testo o un URI che rimanda al testo normale. Lo snippet di codice riportato di seguito verifica questo aspetto, ma mostra solo il codice per la gestione del testo normale:

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

Incolla i dati da un URI dei contenuti

Se l'oggetto ClipData.Item contiene un URI di contenuti e determini che puoi gestire uno dei suoi tipi MIME, crea un elemento ContentResolver e chiama il metodo del provider di contenuti appropriato per recuperare i dati.

La seguente procedura descrive come ottenere i dati da un fornitore di contenuti in base a un URI di contenuto negli appunti. Verifica se un tipo MIME utilizzabile dall'applicazione è disponibile dal provider.

  1. Dichiara che una variabile globale contiene il tipo 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. Scarica gli appunti globali. Richiedi anche un resolver di contenuti per accedere al fornitore di contenuti:

    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. Scarica il clip principale dagli appunti e recupera i contenuti come 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. Verifica se l'URI è un URI dei contenuti richiamando getType(Uri). Questo metodo restituisce un valore nullo se Uri non punta a un fornitore di contenuti valido.

    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. Verifica se il fornitore di contenuti supporta un tipo MIME riconosciuto dall'applicazione. In caso affermativo, chiama ContentResolver.query() per ottenere i dati. Il valore restituito è 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();
                 }
             }
         }
    }
    

Incolla un intent

Per incollare un intent, devi prima recuperare gli appunti globali. Esamina l'oggetto ClipData.Item per vedere se contiene un Intent. Quindi chiama getIntent() per copiare l'intent nel tuo spazio di archiviazione. Lo snippet seguente dimostra che:

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

Notifica di sistema mostrata quando l'app accede ai dati degli appunti

Su Android 12 (livello API 31) e versioni successive, in genere il sistema mostra un messaggio toast quando la tua app chiama getPrimaryClip(). Il testo all'interno del messaggio ha il seguente formato:

APP pasted from your clipboard

Il sistema non mostra un messaggio toast quando l'app esegue una delle seguenti operazioni:

  • Accede a ClipData dalla tua app.
  • Accedi ripetutamente a ClipData da un'app specifica. Il messaggio popup viene visualizzato solo quando l'app accede ai dati dell'app per la prima volta.
  • Recupera i metadati per l'oggetto clip, ad esempio chiamando getPrimaryClipDescription() anziché getPrimaryClip().

Utilizzare fornitori di contenuti per copiare dati complessi

I fornitori di contenuti supportano la copia di dati complessi come record di database o stream di file. Per copiare i dati, inserisci un URI dei contenuti negli appunti. Se incolli le applicazioni, questo URI viene recuperato dagli appunti e utilizzarlo per recuperare i dati del database o i descrittori dei flussi di file.

Poiché l'applicazione Incolla dispone solo dell'URI dei contenuti per i dati, deve sapere quali dati recuperare. Puoi fornire queste informazioni codificando un identificatore per i dati nell'URI stesso oppure puoi fornire un URI univoco che restituisce i dati che vuoi copiare. La tecnica che scegli dipende dall'organizzazione dei tuoi dati.

Le seguenti sezioni descrivono come configurare URI, fornire dati complessi e fornire flussi di file. Le descrizioni presuppongono che tu abbia familiarità con i principi generali della progettazione dei fornitori di contenuti.

Codifica un identificatore sull'URI

Una tecnica utile per copiare i dati negli appunti con un URI è codificare un identificatore per i dati nell'URI stesso. Il tuo fornitore di contenuti può quindi ottenere l'identificatore dall'URI e utilizzarlo per recuperare i dati. L'applicazione per incollare non deve necessariamente sapere che l'identificatore esiste. Deve solo recuperare il "riferimento" (l'URI più l'identificatore) dagli appunti, fornirlo al fornitore di contenuti e recuperare i dati.

In genere codifichi un identificatore su un URI di contenuti concatenandolo alla fine dell'URI. Ad esempio, supponiamo che tu definisca l'URI del provider come la seguente stringa:

"content://com.example.contacts"

Se vuoi codificare un nome in questo URI, utilizza il seguente snippet di codice:

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

Se utilizzi già un fornitore di contenuti, ti consigliamo di aggiungere un nuovo percorso URI che indichi che l'URI deve essere copiato. Ad esempio, supponiamo che tu abbia già i seguenti percorsi URI:

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

Puoi aggiungere un altro percorso per la copia degli URI:

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

Puoi quindi rilevare un URI "copy" mediante la corrispondenza di pattern e gestirlo con un codice specifico per il copia e incolla.

Normalmente utilizzi la tecnica di codifica se già utilizzi un fornitore di contenuti, un database interno o una tabella interna per organizzare i tuoi dati. In questi casi, hai più dati da copiare e presumibilmente un identificatore univoco per ogni dato. In risposta a una query dall'applicazione di incollaggio, puoi cercare i dati in base al relativo identificatore e restituirli.

Se non disponi di più dati, probabilmente non è necessario codificare un identificatore. Puoi utilizzare un URI univoco del tuo provider. In risposta a una query, il provider restituisce i dati attualmente contenuti.

Copia strutture di dati

Configura un fornitore di contenuti per copiare e incollare dati complessi come sottoclasse del componente ContentProvider. Codifica l'URI che inserisci negli appunti in modo che punti al record esatto che vuoi fornire. Inoltre, considera lo stato esistente della tua applicazione:

  • Se hai già un fornitore di contenuti, puoi aumentare le sue funzionalità. Potresti solo dover modificare il metodo query() per gestire gli URI provenienti dalle applicazioni che vogliono incollare dati. Probabilmente vorrai modificare il metodo per gestire un pattern URI "copy".
  • Se l'applicazione gestisce un database interno, ti consigliamo di spostarlo in un fornitore di contenuti per facilitarne la copia.
  • Se non utilizzi un database, puoi implementare un semplice fornitore di contenuti il cui unico scopo è offrire dati alle applicazioni che incollano dagli appunti.

Nel fornitore di contenuti, sostituisci almeno i seguenti metodi:

query()
Se incolli le applicazioni, si presume che possano ottenere i dati utilizzando questo metodo con l'URI inserito negli appunti. Per supportare la copia, fai in modo che questo metodo rilevi gli URI contenenti un percorso "copy" speciale. L'applicazione può quindi creare un URI "copy" da inserire negli appunti, contenente il percorso di copia e un puntatore al record esatto da copiare.
getType()
Questo metodo deve restituire i tipi MIME dei dati che intendi copiare. Il metodo newUri() chiama getType() per inserire i tipi MIME nel nuovo oggetto ClipData.

I tipi MIME per i dati complessi sono descritti in Fornitori di contenuti.

Non è necessario disporre di altri metodi per fornitori di contenuti, come insert() o update(). Un'applicazione che consente di incollare solo gli elementi è sufficiente per recuperare i tipi MIME supportati e copiare i dati dal provider. Se utilizzi già questi metodi, non interferiranno con le operazioni di copia.

I seguenti snippet mostrano come configurare la tua applicazione per copiare dati complessi:

  1. Nelle costanti globali per la tua applicazione, dichiara una stringa URI di base e un percorso che identifica le stringhe URI che utilizzi per copiare i dati. Dichiara anche un tipo MIME per i dati copiati.

    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. Nell'attività da cui gli utenti copiano i dati, configura il codice per copiare i dati negli appunti. In risposta a una richiesta di copia, inserisci l'URI negli appunti.

    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. Nell'ambito globale del tuo fornitore di contenuti, crea un matcher URI e aggiungi un pattern URI che corrisponda agli URI che inserisci negli appunti.

    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. Configura il metodo query(). Questo metodo è in grado di gestire diversi pattern URI, a seconda di come lo codifichi, ma viene visualizzato solo il pattern per l'operazione di copia degli appunti.

    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. Configura il metodo getType() in modo che restituisca un tipo MIME appropriato per i dati copiati:

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

La sezione Incolla i dati da un URI contenuto descrive come ottenere un URI contenuto dagli appunti e utilizzarlo per recuperare e incollare i dati.

Copia stream di dati

Puoi copiare e incollare grandi quantità di testo e dati binari come flussi. I dati possono avere il seguente formato:

  • I file archiviati sul dispositivo reale
  • Flussi da socket
  • Grandi quantità di dati archiviati nel sistema di database sottostante di un provider

Un fornitore di contenuti per gli stream di dati fornisce l'accesso ai propri dati con un oggetto descrittore di file, come AssetFileDescriptor, anziché un oggetto Cursor. L'applicazione Incolla legge il flusso di dati utilizzando questo descrittore del file.

Per configurare l'applicazione in modo che copi uno stream di dati con un provider, segui questi passaggi:

  1. Configura un URI dei contenuti per lo stream di dati che stai inserendo negli appunti. Ecco alcune opzioni:
    • Codifica un identificatore per lo stream di dati nell'URI, come descritto nella sezione Codificare un identificatore sull'URI, quindi gestisci una tabella nel provider che contenga gli identificatori e il nome dello stream corrispondente.
    • Codifica il nome del flusso direttamente sull'URI.
    • Utilizza un URI univoco che restituisca sempre il flusso attuale del provider. Se utilizzi questa opzione, ricordati di aggiornare il tuo provider in modo che punti a uno stream diverso ogni volta che copi lo stream negli appunti utilizzando l'URI.
  2. Fornisci un tipo MIME per ogni tipo di stream di dati che intendi offrire. Le applicazioni che incollano hanno bisogno di queste informazioni per determinare se è possibile incollare i dati negli appunti.
  3. Implementa uno dei metodi ContentProvider che restituisce un descrittore di file per un flusso. Se codifichi gli identificatori sull'URI dei contenuti, utilizza questo metodo per determinare quale stream aprire.
  4. Per copiare lo stream di dati negli appunti, crea l'URI dei contenuti e posizionalo negli appunti.

Per incollare uno stream di dati, un'applicazione ottiene il clip dagli appunti, recupera l'URI e lo utilizza in una chiamata a un metodo di descrittore di file ContentResolver che apre il flusso. Il metodo ContentResolver chiama il metodo ContentProvider corrispondente, trasmettendogli l'URI del contenuto. Il provider restituisce il descrittore del file al metodo ContentResolver. L'applicazione Incolla ha quindi la responsabilità di leggere i dati dal flusso.

Il seguente elenco mostra i metodi più importanti del descrittore di file per un fornitore di contenuti. Ognuno di questi ha un metodo ContentResolver corrispondente con la stringa "Descriptor" aggiunta al nome del metodo. Ad esempio, l'analogico ContentResolver di openAssetFile() è openAssetFileDescriptor().

openTypedAssetFile()

Questo metodo restituisce un descrittore del file di asset, ma solo se il tipo MIME fornito è supportato dal provider. Il chiamante, l'applicazione che sta eseguendo l'operazione, fornisce un pattern di tipo MIME. Il fornitore di contenuti dell'applicazione che copia un URI negli appunti restituisce un handle di file AssetFileDescriptor se può fornire questo tipo MIME e genera un'eccezione se non può farlo.

Questo metodo gestisce le sottosezioni dei file. Puoi utilizzarla per leggere gli asset che il fornitore di contenuti ha copiato negli appunti.

openAssetFile()
Questo metodo è una forma più generica di openTypedAssetFile(). Non filtra i tipi MIME consentiti, ma può leggere le sottosezioni dei file.
openFile()
Si tratta di una forma più generica di openAssetFile(). Non può leggere le sottosezioni dei file.

Facoltativamente, puoi utilizzare il metodo openPipeHelper() con il metodo descrittore del file. In questo modo l'applicazione Incolla può leggere i dati del flusso in un thread in background utilizzando una pipeline. Per utilizzare questo metodo, implementa l'interfaccia ContentProvider.PipeDataWriter.

Progetta funzionalità di copia e incolla efficaci

Per progettare una funzionalità di copia e incolla efficace per la tua applicazione, ricorda i seguenti punti:

  • C'è un solo clip negli appunti in qualsiasi momento. Una nuova operazione di copia da parte di qualsiasi applicazione del sistema sovrascrive il clip precedente. Poiché l'utente potrebbe uscire dall'applicazione e copiarlo prima di tornare, non puoi presumere che gli appunti contengano il clip che l'utente ha precedentemente copiato nella tua applicazione.
  • Lo scopo previsto di più oggetti ClipData.Item per clip è supportare il copia e incolla di più selezioni, anziché diverse forme di riferimento a una singola selezione. In genere, tutti gli oggetti ClipData.Item in un clip devono avere lo stesso formato. Ciò significa che devono essere tutti testo semplice, URI contenuto o Intent e non devono essere misti.
  • Quando fornisci i dati, puoi offrire rappresentazioni MIME diverse. Aggiungi i tipi MIME supportati a ClipDescription, quindi implementa i tipi MIME nel tuo fornitore di contenuti.
  • Quando recupera i dati dagli appunti, l'applicazione è responsabile di controllare i tipi MIME disponibili e di decidere quale, se presente, utilizzare. Anche se negli appunti è presente un clip e l'utente richiede di incollare, l'operazione non è richiesta all'applicazione. Incolla se il tipo MIME è compatibile. Puoi forzare la conversione dei dati negli appunti a testo utilizzando coerceToText(). Se la tua applicazione supporta più di uno dei tipi MIME disponibili, puoi consentire all'utente di scegliere quale utilizzare.