Copiar y pegar

Android proporciona un potente framework basado en el portapapeles para las funciones de copiar y pegar. Admite aplicaciones y tipos de datos complejos, como cadenas de texto, estructuras de datos complejas, texto y flujos binarios datos y recursos de aplicaciones. Los datos de texto simples se almacenan directamente en el portapapeles, pero son complejos los datos se almacenan como una referencia que la aplicación de pegado resuelve con un proveedor de contenido. Copiando y el pegado funciona tanto dentro de una aplicación como entre aplicaciones que implementan el en un framework de aplicaciones.

Dado que parte del framework usa proveedores de contenido, en este documento se da por sentado que ya conoces la API de proveedor de contenido de Android, que se describe en Proveedores de contenido.

Los usuarios esperan retroalimentación cuando copian contenido en el portapapeles, así que, además del framework, potencia la función de copiar y pegar, Android muestra una IU predeterminada a los usuarios cuando realizan copias en Android 13 (nivel de API 33) y versiones posteriores. Debido a esta función, existe el riesgo de notificaciones duplicadas. Puedes obtener más información sobre este caso límite en la Cómo evitar notificaciones duplicadas sección.

Una animación que muestra la notificación del portapapeles de Android 13
Figura 1: IU que se muestra cuando el contenido ingresa al portapapeles en Android 13 y más.

Proporciona comentarios a los usuarios de forma manual cuando copien contenido en Android 12L (nivel de API 32) y versiones anteriores. Consulta recomendaciones para este tema en este documento.

Framework del portapapeles

Cuando uses el framework del portapapeles, coloca los datos en un objeto de clip y, luego, coloca el objeto de clip. en el portapapeles de todo el sistema. El objeto de clip puede tomar una de tres formas:

Texto
Es una string de texto. Coloca la cadena directamente en el objeto de clip, que luego colocas en portapapeles. Para pegar la cadena, obtén el objeto de clip del portapapeles y copia el en el almacenamiento de la aplicación.
URI
Un objeto Uri que representa cualquier formato de URI. Esto se hace principalmente para copiar datos complejos de un proveedor de contenido. Para copiar coloca un objeto Uri en un objeto de clip, colócalo en la portapapeles. Para pegar los datos, obtén el objeto de clip y el objeto Uri. en una fuente de datos, como un proveedor de contenido, y copiar los datos fuente en el almacenamiento de la aplicación.
Intent
Es un Intent. Esta admite la copia de accesos directos a aplicaciones. Para copiar datos, crea un Intent, pon en un objeto de clip y colócalo en el portapapeles. Para pegar los datos, obtén el objeto de clip y, luego, copia el objeto Intent en el archivo área de memoria.

El portapapeles retiene solo un objeto de clip a la vez. Cuando una aplicación coloca un objeto de clip en el portapapeles, el objeto de clip anterior desaparecerá.

Si quieres permitir que los usuarios peguen datos en tu aplicación, no tienes que controlar todos los tipos de datos no estructurados. Puedes examinar los datos del portapapeles antes de darles a los usuarios la opción de pegarlos. Además de tener un determinado formato de datos, el objeto de clip también contiene metadatos que te indican qué MIME tipos de contenedores disponibles. Estos metadatos te ayudan a decidir si tu aplicación puede hacer algo útil con los datos del portapapeles. Por ejemplo, si tienes una aplicación que maneja principalmente texto, puedes es posible que quieras ignorar los objetos de clips que contengan un URI o un intent.

También puedes permitir que los usuarios peguen texto independientemente del formato de datos en el portapapeles. Para fuerza los datos del portapapeles en una representación de texto y, luego, pega ese texto. Este es descrito en la sección Cómo convertir el portapapeles en texto.

Clases del portapapeles

En esta sección, se describen las clases que usa el framework del portapapeles.

ClipboardManager

El portapapeles del sistema Android se representa con el símbolo Clase ClipboardManager. No crees una instancia de esta clase directamente. En su lugar, obtén una referencia invocando getSystemService(CLIPBOARD_SERVICE)

ClipData, ClipData.Item y ClipDescription

Para agregar datos al portapapeles, crea un Un objeto ClipData que contiene una descripción de los datos y los datos en sí. El portapapeles retiene un ClipData a la vez tiempo. Un ClipData contiene un Objeto ClipDescription y una o más Objetos ClipData.Item.

Un objeto ClipDescription contiene metadatos sobre el clip. En particular, contiene un array de los tipos de MIME disponibles para los datos del clip. Además, en Android 12 (nivel de API 31) y versiones posteriores, los metadatos incluyen información sobre si el objeto contiene texto estilizado y acerca del tipo de texto del objeto. Cuando colocas un clip en el portapapeles, esta información está disponible para aplicaciones de pegado, que puede examinar si puede controlar los datos de clips.

Un objeto ClipData.Item contiene los datos de texto, URI o intent:

Texto
A CharSequence.
URI
A Uri. Por lo general, esto contiene un URI de proveedor de contenido, aunque cualquier URI por lo que está permitido. La aplicación que proporciona los datos coloca el URI en el portapapeles. Aplicaciones que quieran pegar los datos, obtener el URI del portapapeles y usarlo para acceder al contenido o en otra fuente de datos y recuperar los datos.
Intent
Es un Intent. Este tipo de datos te permite copiar un acceso directo a la aplicación en la portapapeles. Los usuarios pueden pegar el acceso directo en sus aplicaciones para usarlo más tarde.

Puedes agregar más de un objeto ClipData.Item a un clip. Esto permite a los usuarios copiar y pegar varias selecciones como un solo clip. Por ejemplo, si tienes un widget de lista que permite usuario selecciona más de un elemento a la vez, puedes copiar todos los elementos en el portapapeles a la vez. Tareas pendientes crea un ClipData.Item separado para cada elemento de la lista y, luego, agrega el ClipData.Item al objeto ClipData.

Métodos de conveniencia de ClipData

La clase ClipData proporciona métodos de conveniencia estáticos para crear un Un objeto ClipData con un solo objeto ClipData.Item y un Objeto ClipDescription:

newPlainText(label, text)
Muestra un objeto ClipData cuyo único objeto ClipData.Item. contiene una cadena de texto. La etiqueta del objeto ClipDescription está configurada en label El único tipo de MIME en ClipDescription es MIMETYPE_TEXT_PLAIN.

Usa newPlainText() para crear un clip a partir de una cadena de texto.

newUri(resolver, label, URI)
Muestra un objeto ClipData cuyo único objeto ClipData.Item. contiene un URI. La etiqueta del objeto ClipDescription está configurada en label Si el URI es de contenido, es decir, si Uri.getScheme() muestra content:; el método usa el ContentResolver objeto proporcionado en resolver para recuperar los tipos de MIME disponibles de la proveedor de contenido. Luego, los almacena en ClipDescription. Para un URI que no es un URI content:, el método establece el tipo de MIME en MIMETYPE_TEXT_URILIST

Usa newUri() para crear un clip a partir de un URI, en particular un URI de content:.

newIntent(label, intent)
Muestra un objeto ClipData cuyo único objeto ClipData.Item. contiene un Intent. La etiqueta del objeto ClipDescription está configurada en label El tipo de MIME está configurado en MIMETYPE_TEXT_INTENT

Usa newIntent() para crear un clip a partir de un objeto Intent.

Convertir los datos del portapapeles en texto

Incluso si tu aplicación solo maneja texto, puedes copiar datos que no sean texto del portapapeles. Para ello, haz lo siguiente: lo convierte con el ClipData.Item.coerceToText() .

Este método convierte los datos de ClipData.Item en texto y muestra un CharSequence El valor que muestra ClipData.Item.coerceToText() se basa sobre el formato de datos en ClipData.Item:

Texto
Si ClipData.Item es texto, es decir, getText() no es nulo: coerceToText() muestra el texto.
URI
Si ClipData.Item es un URI, es decir, si getUri() no es nulo: coerceToText() intenta usarlo como un URI de contenido.
  • Si se trata de un URI de contenido y el proveedor puede devolver una transmisión de texto coerceToText() muestra un flujo de texto.
  • Si se trata de un URI de contenido, pero el proveedor no ofrece una transmisión de texto, coerceToText() muestra una representación del URI. La representación es iguales que devuelve Uri.toString()
  • Si un URI no es de contenido, coerceToText() muestra una representación de el URI. La representación es igual que la que devuelve Uri.toString()
Intent
Si ClipData.Item es un Intent, es decir, si getIntent() no es nulo: coerceToText() lo convierte en un URI de intent y lo muestra. La representación es igual que la que devuelve Intent.toUri(URI_INTENT_SCHEME)

El framework del portapapeles se resume en la Figura 2. Para copiar datos, una aplicación pone una Objeto ClipData en el portapapeles global ClipboardManager. El ClipData contiene uno o más objetos ClipData.Item y uno Objeto ClipDescription. Para pegar datos, una aplicación obtiene el elemento ClipData, obtiene su tipo de MIME de ClipDescription y obtiene los datos de ClipData.Item o del proveedor de contenido al que hace referencia ClipData.Item

Una imagen que muestra un diagrama de bloques del marco de trabajo para copiar y pegar
. Figura 2: Marco de trabajo del portapapeles de Android

Copiar en el portapapeles

Para copiar datos en el portapapeles, obtén un controlador para el objeto ClipboardManager global. Crea un objeto ClipData y agrega un ClipDescription y uno o más. ClipData.Item. Agrega el objeto ClipData terminado al ClipboardManager. Esto se describe con más detalle en el siguiente procedimiento:

  1. Si copias datos con un URI de contenido, configura un proveedor de contenido.
  2. Obtén el portapapeles del 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 los datos a un nuevo objeto ClipData:

    • Para texto

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

      Este fragmento crea un URI codificando un ID de registro en el URI de contenido de el proveedor. Esta técnica se aborda con más detalle en el Sección Codifica un identificador en el 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);
      
    • Para un intent

      Este fragmento construye un Intent para una aplicación y, luego, coloca en el objeto de 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. Coloca el nuevo objeto de clip en el portapapeles:

    Kotlin

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

    Java

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

Cómo hacer comentarios cuando se copia contenido al portapapeles

Los usuarios esperan alguna clase de comentario visual cuando una app copia contenido al portapapeles. Ya se completó este paso automáticamente para los usuarios de Android 13 y versiones posteriores, pero debe implementarse manualmente en versiones anteriores versiones.

A partir de Android 13, el sistema muestra una confirmación visual estándar cuando se agrega contenido en el portapapeles. La nueva confirmación hace lo siguiente:

  • Confirma que el contenido se copió de forma correcta.
  • Proporciona una vista previa del contenido copiado.

Una animación que muestra la notificación del portapapeles de Android 13
Figura 3: IU que se muestra cuando el contenido ingresa al portapapeles en Android 13 y más.

En Android 12L (nivel de API 32) y versiones anteriores, es posible que los usuarios no sepan si copiaron correctamente contenido contenido o lo que copiaron. Esta función estandariza las distintas notificaciones que muestran las apps después de copiar y ofrece a los usuarios más control sobre el portapapeles.

Cómo evitar notificaciones duplicadas

En Android 12L (nivel de API 32) y versiones anteriores, recomendamos alertar a los usuarios cuando copien contenido correctamente a través de la emisión de comentarios visuales en la app con un widget como Toast o Una Snackbar, después de copiar.

Para evitar que la información se muestre duplicada, te recomendamos que quites los avisos o barras de notificaciones que se muestran después de una copia en la app para Android 13 y versiones posteriores.

Se muestra una barra de notificaciones después de una copia en la app.
Figura 4: Si muestras una barra de notificaciones de confirmación de copia en Android 13, el usuario ve mensajes duplicados.
Se muestra un aviso después de una copia en la app.
. Figura 5: Si muestras un aviso de confirmación de copia en Android 13, el usuario ve mensajes duplicados.

A continuación, se muestra un ejemplo para hacerlo:

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

Cómo agregar contenido sensible al portapapeles

Si tu app permite que los usuarios copien contenido sensible, como contraseñas o datos de crédito, en el portapapeles información de la tarjeta, debes agregar una marca a ClipDescription en ClipData antes de llamar a ClipboardManager.setPrimaryClip(). Agregar esta marca evita para evitar que aparezca contenido en la confirmación visual de contenido copiado en Android 13 y versiones posteriores.

Vista previa del texto copiado sin marcas en el contenido sensible
Figura 6: Vista previa del texto copiado sin una marca de contenido sensible.
Vista previa del texto copiado con marcas en el contenido sensible.
. Figura 7: Vista previa del texto copiado con una marca de contenido sensible.

Para marcar contenido sensible, agrega un valor booleano adicional a ClipDescription. Todas las apps deben independientemente del nivel de API objetivo.

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

Pegar desde el portapapeles

Como se describió anteriormente, pega datos desde el portapapeles. Para ello, obtén el objeto del portapapeles global. obtener el objeto de clip, mirar sus datos y, si es posible, copiar los datos del objeto de clip en tu propio almacenamiento. Esta sección explica en detalle cómo pegar los tres formatos del portapapeles de datos no estructurados.

Pegar texto sin formato

Para pegar texto sin formato, obtén el portapapeles global y verifica que pueda mostrar texto sin formato. Luego, obtén el objeto de clip y copia su texto a tu propio almacenamiento usando getText(), como se describe en el siguiente procedimiento:

  1. Obtén el objeto ClipboardManager global con getSystemService(CLIPBOARD_SERVICE) Además, declara una variable global para que contenga el texto pegado:

    Kotlin

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

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
    
  2. Determina si necesitas habilitar o inhabilitar la función "pegar" en la configuración actividad. Verifica que el portapapeles contenga un clip y que puedas controlar el tipo de datos representado por el 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 los datos del portapapeles. Solo se puede acceder a este punto del código si el "pegar" el elemento de menú está habilitado, así que puedes suponer que el portapapeles contiene archivos texto. Aún no sabes si contiene una cadena de texto o un URI que apunta a texto sin formato. En el siguiente fragmento de código, se prueba, pero solo se muestra el código para manejar texto sin formato:

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

Pega datos desde un URI de contenido

Si el objeto ClipData.Item contiene un URI de contenido y determinas que puedes controlar uno de sus tipos de MIME, crear un ContentResolver y llamar al contenido correspondiente del proveedor para recuperar los datos.

El siguiente procedimiento describe cómo obtener datos de un proveedor de contenido en función de un URI de contenido en el portapapeles. Comprueba si un tipo de MIME que la aplicación puede usar está disponible en la proveedor.

  1. Declara una variable global para que incluya un tipo de 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. Obtén el portapapeles global. Además, obtén un agente de resolución de contenido para poder acceder al proveedor de contenido:

    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. Obtén el clip principal del portapapeles y su contenido como un 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. Prueba si el URI es de contenido llamando getType(Uri) Este método muestra un resultado nulo si Uri no apunta a un proveedor de contenido válido.

    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. Comprueba si el proveedor de contenido admite un tipo de MIME que la aplicación comprenda. Si sí, llama ContentResolver.query() para obtener los datos. El valor que se devuelve es un 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();
                 }
             }
         }
    }
    

Pegar un intent

Para pegar un intent, primero obtén el portapapeles global. Examina el objeto ClipData.Item para ver si contiene un Intent. Luego, llama a getIntent() para copiar el en tu propio almacenamiento. Esto se demuestra en el siguiente fragmento:

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

La notificación del sistema se muestra cuando la app accede a los datos del portapapeles

En Android 12 (nivel de API 31) y versiones posteriores, por lo general, el sistema muestra un mensaje de aviso cuando la app llamadas getPrimaryClip() El texto dentro de este mensaje contiene el siguiente formato:

APP pasted from your clipboard

El sistema no muestra un mensaje de aviso cuando la app realiza una de las siguientes acciones:

  • Accesos ClipData de tu propia app.
  • Accede de forma reiterada a ClipData desde una app específica. El aviso solo aparece cuando tu app accede a los datos de esa app por primera vez.
  • Recupera los metadatos del objeto de clip, por ejemplo, a través de una llamada getPrimaryClipDescription() en lugar de getPrimaryClip().

Usar proveedores de contenido para copiar datos complejos

Los proveedores de contenido admiten la copia de datos complejos como registros de bases de datos o transmisiones de archivos. Para copiar coloca un URI de contenido en el portapapeles. Las aplicaciones de pegado obtienen este URI del portapapeles y úsalo para recuperar datos de bases de datos o descriptores de flujo de archivos.

Como la aplicación de pegado solo tiene el URI de contenido para tus datos, necesita saber qué parte de los datos por recuperar. Puedes proporcionar esta información codificando un identificador para los datos en el URI, o puedes proporcionar un URI único que muestre los datos que quieres copiar. Cuál técnica que elijas depende de la organización de tus datos.

En las siguientes secciones, se describe cómo configurar URIs, proporcionar datos complejos y proporcionar archivos transmisiones continuas. Las descripciones suponen que estás familiarizado con los principios generales del proveedor de contenido el diseño de tu producto.

Codifica un identificador en el URI

Una técnica útil para copiar datos en el portapapeles con un URI es codificar un identificador para los datos en el URI mismo. Tu proveedor de contenido puede obtener el identificador del URI y usar para recuperar los datos. La aplicación de pegado no necesita saber que el identificador existe. Integra solo necesita obtener su "referencia" (el URI más el identificador) de la el portapapeles, le asignas tu proveedor de contenido y recuperas los datos.

Por lo general, para codificar un identificador en un URI de contenido, se lo concatena al final del URI. Por ejemplo, supón que defines el URI de tu proveedor como la siguiente string:

"content://com.example.contacts"

Si quieres codificar un nombre en este URI, usa el siguiente fragmento de código:

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

Si ya usas un proveedor de contenido, quizás quieras agregar una nueva ruta de URI que indique el URI es para copiar. Por ejemplo, supón que ya tienes las siguientes rutas de URI:

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

Puedes agregar otra ruta de acceso para copiar URIs:

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

Puedes detectar una "copia" por coincidencia de patrones y controlarlo con un código específicas para copiar y pegar.

Por lo general, se utiliza la técnica de codificación si ya se emplea un proveedor de contenido, base de datos o tabla interna para organizar tus datos. En estos casos, tienes varios tipos de datos que quieres copiar y, posiblemente, un identificador único para cada pieza. En respuesta a una consulta del aplicación de pegado, puede buscar los datos por su identificador y mostrarlos.

Si no tienes muchos datos, es probable que no necesites codificar un identificador. Puedes usar un URI que sea único para tu proveedor. En respuesta a una consulta, tu proveedor devuelve el de los datos que contiene actualmente.

Cómo copiar estructuras de datos

Configura un proveedor de contenido para copiar y pegar datos complejos como una subclase de la ContentProvider este componente. Codifica el URI que colocaste en el portapapeles para que apunte al registro exacto que quieres. proporcionan. Además, considera el estado existente de tu aplicación:

  • Si ya tienes un proveedor de contenido, puedes agregarlo a su funcionalidad. Es posible que solo debes modificar su método query() para manejar los URIs provenientes de aplicaciones que si quieres pegar datos. Es probable que quieras modificar el método para manejar una “copia” URI .
  • Si tu aplicación mantiene una base de datos interna, es posible que desees moverla a un proveedor de contenido para facilitar el copiado.
  • Si no estás usando una base de datos, puedes implementar un proveedor de contenido simple cuyo único propósito es ofrecer datos a las aplicaciones que pegan contenido desde el portapapeles.

En el proveedor de contenido, anula al menos los siguientes métodos:

query()
Las aplicaciones de pegado suponen que pueden obtener tus datos usando este método con el URI que colocar en el portapapeles. Para admitir la copia, haz que este método detecte los URI que contengan un elemento "copiar" ruta de acceso. Luego, tu aplicación puede crear una “copia” URI para colocar en el portapapeles, que contiene la ruta de acceso para copiar y un puntero al registro exacto que deseas copiar.
getType()
Este método debe mostrar los tipos de MIME de los datos que quieres copiar. El método newUri() Llama a getType() para colocar los tipos de MIME en el nuevo ClipData. .

Los tipos de MIME para datos complejos se describen en Proveedores de contenido.

No es necesario que tengas ninguno de los otros métodos de proveedor de contenido, como insert() o update() Una aplicación de pegado solo necesita obtener tus tipos de MIME compatibles y copiar datos de tu proveedor. Si ya tienes esos métodos, no interferirán con las operaciones de copiado.

En los siguientes fragmentos, se muestra cómo configurar tu aplicación para copiar datos complejos:

  1. En las constantes globales de tu aplicación, declara una cadena de URI base y una ruta de acceso que identifica las cadenas de URI que usas para copiar datos. Declara también un tipo de MIME para los de datos no estructurados.

    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. En la actividad desde la que los usuarios copian datos, configura el código para copiar datos en el portapapeles. En respuesta a una solicitud de copia, coloca el URI en el portapapeles.

    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. En el alcance global de tu proveedor de contenido, crea un comparador de URI y agrega un patrón de URI que coincide con los URI que colocaste en el portapapeles.

    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 query() . Este método puede controlar diferentes patrones de URI, según cómo lo codifiques, pero solo muestra el patrón de la operación de copia del portapapeles.

    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 el método getType() a fin de mostrar un tipo de MIME adecuado para el contenido copiado. datos:

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

En la sección Cómo pegar datos desde un URI de contenido, se describe cómo obtener un el URI de contenido del portapapeles y usarlo para obtener y pegar datos.

Cómo copiar flujos de datos

Puedes copiar y pegar grandes cantidades de datos binarios y de texto como transmisiones. Los datos pueden tener formularios como los siguientes:

  • Archivos almacenados en el dispositivo real
  • Transmisiones desde sockets
  • Grandes cantidades de datos almacenados en el sistema de base de datos subyacente de un proveedor

Un proveedor de contenido para flujos de datos proporciona acceso a sus datos con un objeto descriptor de archivos, como, por ejemplo, AssetFileDescriptor, en lugar de un objeto Cursor. La aplicación de pegado lee el flujo de datos con este descriptor de archivos.

Si deseas configurar tu aplicación para que copie un flujo de datos con un proveedor, sigue estos pasos:

  1. Configura un URI de contenido para la transmisión de datos que quieras colocar en el portapapeles. Para ello, existen las siguientes opciones:
    • Codifica un identificador para el flujo de datos en el URI, como se describe en el Codifica un identificador en la sección URI y, luego, mantén un de tu proveedor que contenga identificadores y el nombre de transmisión correspondiente.
    • Codifica el nombre de la transmisión directamente en el URI.
    • Usa un URI único que siempre muestre la transmisión actual del proveedor. Si usar esta opción, recuerda actualizar tu proveedor para que dirija a una transmisión diferente cada vez que copies la transmisión en el portapapeles con el URI.
  2. Proporciona un tipo de MIME para cada tipo de transmisión de datos que planees ofrecer. Pegando aplicaciones necesitan esta información para determinar si pueden pegar los datos en el portapapeles.
  3. Implementa uno de los métodos ContentProvider que muestra un descriptor de archivos para de un flujo. Si codificas identificadores en el URI de contenido, usa este método para determinar qué flujo para abrirlo.
  4. Para copiar el flujo de datos en el portapapeles, crea el URI de contenido y colócalo en el portapapeles.

Para pegar un flujo de datos, una aplicación obtiene el clip del portapapeles, obtiene el URI y usa en una llamada a un método del descriptor de archivos ContentResolver que abre la transmisión. El El método ContentResolver llama al método ContentProvider correspondiente. y pasarle el URI de contenido. Tu proveedor devuelve el descriptor de archivos al ContentResolver. La aplicación de pegado tiene la responsabilidad de leer el los datos del flujo.

En la siguiente lista, se muestran los métodos más importantes del descriptor de archivos para un proveedor de contenido. Cada de estos tienen un método ContentResolver correspondiente con la cadena "Descriptor" al nombre del método. Por ejemplo, ContentResolver. análogo de openAssetFile() es openAssetFileDescriptor()

openTypedAssetFile()

Este método devuelve un descriptor de archivos de elementos, pero solo si se proporciona el tipo de MIME compatibles con el proveedor. El llamador (la aplicación que realiza el pegado) proporciona un patrón de tipo de MIME. El proveedor de contenido de la aplicación que copia un URI en el el portapapeles muestra un controlador de archivo AssetFileDescriptor si puede proporcionarlo Es el tipo de MIME y arroja una excepción si no puede hacerlo.

Este método maneja las subsecciones de los archivos. Puedes usarlo para leer los recursos que proveedor de contenido se copió en el portapapeles.

openAssetFile()
Este método es un formato más general de openTypedAssetFile(). No filtra para los tipos de MIME permitidos, pero puede leer subsecciones de archivos.
openFile()
Es un formato más general de openAssetFile(). No puede leer subsecciones de archivos.

De manera opcional, puedes usar la openPipeHelper() con tu método del descriptor de archivos. Esto permite que la aplicación de pegado lea los datos de transmisión de una subproceso en segundo plano con una canalización. Para usar este método, implementa la ContentProvider.PipeDataWriter interfaz de usuario.

Diseña una funcionalidad efectiva de copiar y pegar

Si quieres diseñar una funcionalidad efectiva de copiar y pegar para tu aplicación, recuerda estos puntos:

  • En todo momento, solo hay un clip en el portapapeles. Una nueva operación de copia por cualquier del sistema reemplaza el clip anterior. Como el usuario podría navegar de tu aplicación y copiarlos antes de regresar, no puedes asumir que el portapapeles Contiene el clip que el usuario copió previamente en tu aplicación.
  • El propósito previsto de varios objetos ClipData.Item por clip es admiten la copia y el pegado de múltiples selecciones en lugar de diferentes formas de referencia a una sola selección. Por lo general, quieres que todos los ClipData.Item en un clip para que tengan la misma forma. Es decir, todas deben contener texto simple, contenido URI, o Intent, y no mezclados.
  • Cuando proporcionas datos, puedes ofrecer diferentes representaciones de MIME. Agrega los tipos de MIME admites el ClipDescription y, luego, implementas los tipos de MIME en tu proveedor de contenido.
  • Cuando obtienes datos del portapapeles, tu aplicación es responsable de verificar los tipos de MIME disponibles y, luego, decidir cuál usar, si los hubiera. Incluso si hay en el portapapeles y el usuario solicita la acción de pegar, la aplicación no es necesaria. para pegar el contenido. Realiza la acción de pegado si el tipo de MIME es compatible. Podrías forzar a los datos del portapapeles a texto con coerceToText(). Si tu aplicación admite más de uno de los tipos de MIME disponibles, puedes permitir que el usuario elija cuál usar.