Copiar e colar

Experimente trabalhar com o Compose
O Jetpack Compose é o kit de ferramentas de IU recomendado para Android. Saiba como usar copiar e colar no Compose.

O Android fornece um poderoso framework baseado em área de transferência para os procedimentos de copiar e colar. Ele é compatível com tipos de dados simples e complexos, incluindo strings de texto, estruturas de dados complexas, dados de stream de texto e binários e recursos de aplicativos. Os dados de texto simples são armazenados diretamente na área de transferência, enquanto os dados complexos são armazenados como uma referência que o aplicativo de colagem resolve com um provedor de conteúdo. As ações de copiar e colar funcionam tanto em um aplicativo quanto entre aplicativos que implementam o framework.

Como parte do framework usa provedores de conteúdo, este documento pressupõe certa familiaridade com a API Android Content Provider, que é descrita em Provedores de conteúdo.

Os usuários esperam feedback ao copiar conteúdo para a área de transferência. Portanto, além do framework que permite copiar e colar, o Android mostra uma IU padrão quando usuários executam ações de cópia no Android 13 (nível da API 33) e versões mais recentes. Devido a esse recurso, há o risco de notificações duplicadas. Saiba mais sobre esse caso extremo na seção Evitar notificações duplicadas.

Animação mostrando a notificação da área de transferência do Android 13
Figura 1. IU exibida quando o conteúdo é inserido na área de transferência no Android 13 e versões mais recentes.

Envie feedback manualmente aos usuários que executam ações de cópia no Android 12L (nível da API 32) e versões anteriores. Consulte as recomendações para isso neste documento.

O framework da área de transferência

Ao usar o framework da área de transferência, coloque os dados em um objeto de clipe e, em seguida, coloque o objeto de clipe na área de transferência do sistema. O objeto de transferência pode assumir uma destas três formas:

Texto
Uma string de texto. Coloque a string diretamente no objeto de clipe, que é colocado na área de transferência. Para colar a string, pegue o objeto de transferência da área de transferência e copie a string para o armazenamento do aplicativo.
URI
Um objeto Uri que representa qualquer forma de URI. Isso é usado principalmente para copiar dados complexos de um provedor de conteúdo. Para copiar dados, coloque um objeto Uri em um objeto de transferência e coloque o objeto de transferência na área de transferência. Para colar os dados, pegue o objeto de transferência, pegue o objeto Uri, resolva-o para uma fonte de dados, como um provedor de conteúdo, e copie os dados da fonte para o armazenamento do aplicativo.
Intenção
uma Intent. Ela oferece suporte à cópia de atalhos de aplicativos. Para copiar dados, crie um Intent, coloque-o em um objeto de clipe e coloque o objeto de clipe na área de transferência. Para colar os dados, acesse o objeto de transferência e copie o objeto Intent para a área de memória do aplicativo.

A área de transferência mantém apenas um objeto de transferência por vez. Quando um aplicativo coloca um objeto de transferência na área de transferência, o objeto anterior desaparece.

Se você quiser permitir que os usuários colem dados no aplicativo, não será necessário processar todos os tipos de dados. É possível examinar os dados na área de transferência antes de dar aos usuários a opção de colá-los. Além de ter um determinado formato de dados, o objeto de clipe também contém metadados que informam quais tipos MIME estão disponíveis. Esses metadados ajudam a decidir se o aplicativo pode fazer algo útil com os dados da área de transferência. Por exemplo, caso você tenha um aplicativo que lida principalmente com texto, pode ignorar objetos de transferência que contenham um URI ou uma intent.

Também é possível permitir que os usuários colem texto, independentemente da forma dos dados na área de transferência. Para fazer isso, force os dados da área de transferência para uma representação de texto e cole esse texto. Isso é descrito na seção Forçar os dados da área de transferência a se tornarem texto.

Classes da área de transferência

Esta seção descreve as classes usadas pelo framework da área de transferência.

ClipboardManager

A área de transferência do sistema Android é representada pela classe global ClipboardManager. Não instancie essa classe diretamente. Em vez disso, invoque getSystemService(CLIPBOARD_SERVICE) para receber uma referência.

ClipData, ClipData.Item e ClipDescription

Para adicionar dados à área de transferência, crie um objeto ClipData que contenha uma descrição dos dados e os dados em si. A área de transferência contém um ClipData por vez. Um ClipData contém um objeto ClipDescription e um ou mais objetos ClipData.Item.

Um objeto ClipDescription contém metadados sobre a transferência. Em particular, ele contém uma matriz de tipos MIME disponíveis para os dados do clipe. Além disso, no Android 12 (nível 31 da API) e versões mais recentes, os metadados incluem informações sobre se o objeto contém texto estilizado e sobre o tipo de texto no objeto. Quando você coloca um clipe na área de transferência, essas informações ficam disponíveis para os aplicativos de colagem, que podem analisar se conseguem processar os dados do clipe.

Um objeto ClipData.Item contém os dados de texto, URI ou intent:

Texto
Um CharSequence.
URI
Um Uri. Normalmente, ele contém um URI de provedor de conteúdo, embora qualquer URI seja permitido. O aplicativo que fornece os dados coloca o URI na área de transferência. Os aplicativos que querem colar os dados recebem o URI da área de transferência e o usam para acessar o provedor de conteúdo ou outra fonte de dados e recuperar os dados.
Intenção
Uma Intent. Esse tipo de dado permite copiar um atalho de aplicativo para a área de transferência. Então, os usuários podem colar o atalho nos aplicativos deles para uso posterior.

Você pode adicionar mais de um objeto ClipData.Item a uma transferência. Isso permite que os usuários copiem e colem várias seleções como um único clipe. Por exemplo, se você tiver um widget de lista que permite ao usuário selecionar mais de um item por vez, poderá copiar todos os itens para a área de transferência de uma só vez. Para fazer isso, crie um ClipData.Item separado para cada item da lista e adicione os objetos ClipData.Item ao objeto ClipData.

Métodos de conveniência ClipData

A classe ClipData fornece métodos estáticos de conveniência para criar um objeto ClipData com um único objeto ClipData.Item e um objeto ClipDescription simples:

newPlainText(label, text)
Retorna um objeto ClipData cujo único objeto ClipData.Item contém uma string de texto. O rótulo do objeto ClipDescription está definido como label. O tipo MIME único em ClipDescription é MIMETYPE_TEXT_PLAIN.

Use newPlainText() para criar um clipe de uma string de texto.

newUri(resolver, label, URI)
Retorna um objeto ClipData cujo único objeto ClipData.Item contém um URI. O rótulo do objeto ClipDescription está definido como label. Se o URI for de conteúdo, ou seja, se Uri.getScheme() retornar content:, o método vai usar o objeto ContentResolver fornecido em resolver para extrair os tipos MIME disponíveis no provedor de conteúdo. Em seguida, ele os armazena em ClipDescription. Para um URI que não é um URI content:, o método define o tipo MIME como MIMETYPE_TEXT_URILIST.

Use newUri() para criar um clipe de um URI, principalmente um URI content:.

newIntent(label, intent)
Retorna um objeto ClipData cujo único objeto ClipData.Item contém uma Intent. O rótulo do objeto ClipDescription está definido como label. O tipo MIME é definido como MIMETYPE_TEXT_INTENT.

Use newIntent() para criar um clipe de um objeto Intent.

Forçar os dados da área de transferência para texto

Mesmo que o aplicativo processe apenas texto, é possível copiar dados que não sejam de texto da área de transferência convertendo-os com o método ClipData.Item.coerceToText().

Esse método converte os dados de ClipData.Item em texto e retorna uma CharSequence. O valor que ClipData.Item.coerceToText() retorna é baseado na forma dos dados em ClipData.Item:

Texto
Se ClipData.Item for texto, ou seja, se getText() não for nulo, coerceToText() vai retornar o texto.
URI
Se ClipData.Item for um URI, ou seja, se getUri() não for nulo, o método coerceToText() vai tentar usá-lo como um URI de conteúdo.
  • Se o URI for de conteúdo e o provedor puder retornar um stream de texto, coerceToText() vai retornar um stream de texto.
  • Se o URI for de conteúdo, mas o provedor não oferecer um stream de texto, coerceToText() vai retornar uma representação do URI. A representação é a mesma retornada por Uri.toString().
  • Se o URI não for de conteúdo, coerceToText() vai retornar uma representação dele. A representação é igual à retornada por Uri.toString().
Intenção
Se ClipData.Item for uma Intent, ou seja, se getIntent() não for nulo, coerceToText() vai convertê-la em um URI de intent e a retornar. A representação é igual à retornada por Intent.toUri(URI_INTENT_SCHEME).

O framework da área de transferência é resumido na figura 2. Para copiar dados, um aplicativo coloca um objeto ClipData na área de transferência global ClipboardManager. O ClipData contém um ou mais objetos ClipData.Item e um objeto ClipDescription. Para colar dados, um aplicativo recebe o ClipData, o tipo MIME do ClipDescription e os dados do ClipData.Item ou do provedor de conteúdo referenciado pelo ClipData.Item.

Uma imagem mostrando um diagrama de blocos do framework de copiar e colar
Figura 2. O framework da área de transferência do Android.

Copiar para a área de transferência

Para copiar dados para a área de transferência, use um handle para o objeto global ClipboardManager, crie um objeto ClipData e adicione um ClipDescription e um ou mais objetos ClipData.Item a ele. Em seguida, adicione o objeto ClipData finalizado ao objeto ClipboardManager. Isso é descrito em mais detalhes no procedimento a seguir:

  1. Se você estiver copiando dados usando um URI de conteúdo, configure um provedor de conteúdo.
  2. Acesse a área de transferência do 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. Copie os dados para um novo 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 um URI

      Esse snippet cria um URI codificando um ID de registro no URI de conteúdo para o provedor. Essa técnica é abordada com mais detalhes na seção Codificar um identificador no 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 uma intent

      Este snippet cria um Intent para um aplicativo e o coloca no objeto de clipe:

      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. Coloque o novo objeto de transferência na área de transferência:

    Kotlin

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

    Java

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

Enviar feedback durante a realização de cópias para a área de transferência

Os usuários esperam feedback visual quando usam um app para copiar conteúdo para a área de transferência. Isso é feito automaticamente para usuários no Android 13 e versões mais recentes, mas precisa ser implementado manualmente em versões anteriores.

No Android 13 e versões mais recentes, o sistema mostra uma confirmação visual padrão ao adicionar conteúdo à área de transferência. A nova confirmação faz o seguinte:

  • Confirma que o conteúdo foi copiado.
  • Oferece uma visualização do conteúdo copiado.

Animação mostrando a notificação da área de transferência do Android 13
Figura 3. IU exibida quando o conteúdo é inserido na área de transferência no Android 13 e versões mais recentes.

No Android 12L (nível 32 da API) e versões anteriores, os usuários podem não ter certeza se copiaram o conteúdo ou o que copiaram. Esse recurso padroniza as várias notificações exibidas pelos apps após copiar um conteúdo e oferece aos usuários mais controle sobre a área de transferência.

Evitar notificações duplicadas

No Android 12L (nível 32 da API) e versões anteriores, recomendamos alertar os usuários quando eles copiarem algo com um feedback visual no app, usando um widget como um Toast ou um Snackbar, após a cópia.

Para não mostrar informações duplicadas, recomendamos que você remova as notificações de aviso ou as snackbars exibidas quando o usuário copia um conteúdo no app no Android 13 e versões mais recentes.

Exibir snackbar depois que o usuário cópia conteúdo no app.
Figura 4. Se o app exibir uma snackbar de confirmação de cópia no Android 13, o usuário verá mensagens duplicadas.
Exibir aviso depois que o usuário copia conteúdo no app.
Figura 5. Se o app exibir um aviso de confirmação de cópia no Android 13, o usuário verá mensagens duplicadas.

Veja como fazer isso:

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

Adicionar conteúdo sensível à área de transferência

Se o app permitir que os usuários copiem conteúdo confidencial para a área de transferência, como senhas ou dados de cartões de crédito, adicione uma sinalização à ClipDescription em ClipData antes de chamar ClipboardManager.setPrimaryClip(). Essa sinalização impede que conteúdo sensível seja exibido na confirmação visual de conteúdo copiado no Android 13 e versões mais recentes.

Visualização do texto copiado sem sinalização de conteúdo sensível.
Figura 6. Visualização do texto copiado sem sinalização de conteúdo sensível.
Visualização do texto copiado com sinalização de conteúdo sensível.
Figura 7. Visualização do texto copiado com uma sinalização de conteúdo sensível.

Para sinalizar a presença de conteúdo sensível, adicione um booleano extra à ClipDescription. Isso precisa ser feito por todos os apps, independentemente do nível desejado da API.

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

Colar da área de transferência

Como descrito anteriormente, para colar os dados da área de transferência, pegue o objeto global da área de transferência, pegue o objeto de transferência, observe seus dados e, se possível, copie os dados do objeto de transferência para seu próprio armazenamento. Esta seção explica em detalhes como colar os três formatos de dados da área de transferência.

Colar texto simples

Para colar texto simples, acesse a área de transferência global e verifique se ela pode retornar texto simples. Em seguida, acesse o objeto de clipe e copie o texto para seu próprio armazenamento usando getText(), conforme descrito no procedimento a seguir:

  1. Acesse o objeto global ClipboardManager usando getSystemService(CLIPBOARD_SERVICE). Além disso, declare uma variável global para conter o texto colado:

    Kotlin

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

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
  2. Determine se você precisa ativar ou desativar a opção "colar" na atividade atual. Verifique se a área de transferência contém um clipe e se você pode processar o tipo de dados representado por ele:

    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. Copie os dados da área de transferência. Esse ponto no código só pode ser alcançado se o item de menu "Colar" estiver ativado. Portanto, você pode presumir que a área de transferência contém texto simples. Você ainda não sabe se ele contém uma string de texto ou um URI que aponta para um texto simples. O snippet de código a seguir testa isso, mas mostra apenas o código para processar texto simples:

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

Colar dados de um URI de conteúdo

Se o objeto ClipData.Item tiver um URI de conteúdo e você determinar que pode processar um dos tipos MIME dele, crie um ContentResolver e chame o método de provedor de conteúdo adequado para recuperar os dados.

O procedimento a seguir descreve como receber dados de um provedor de conteúdo com base em um URI de conteúdo na área de transferência. Ele verifica se um tipo MIME que o aplicativo pode usar está disponível no provedor.

  1. Declare uma variável global para conter o 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. Pegue a área de transferência global. Receba também um resolvedor de conteúdo para acessar o provedor de conteúdo:

    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. Extraia o clipe principal da área de transferência e o conteúdo dele como um 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. Teste se o URI é um URI de conteúdo chamando getType(Uri). Esse método vai retornar nulo se Uri não apontar para um provedor de conteúdo 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. Teste se o provedor de conteúdo é compatível com um tipo MIME que o aplicativo entende. Se houver, chame ContentResolver.query() para receber os dados. O valor de retorno é um 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();
                 }
             }
         }
    }

Colar uma intent

Para colar uma intent, primeiro acesse a área de transferência global. Examine o objeto ClipData.Item para ver se ele contém um Intent. Em seguida, chame getIntent() para copiar a intent para seu próprio armazenamento. O snippet a seguir demonstra isso:

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ção do sistema exibida quando o app acessa dados da área de transferência

No Android 12 (nível 31 da API) e versões mais recentes, o sistema geralmente mostra uma mensagem de aviso quando o app chama getPrimaryClip(). O texto da mensagem contém o formato a seguir:

APP pasted from your clipboard

O sistema não mostra uma mensagem de aviso quando o app faz o seguinte:

  • Acessa ClipData do seu próprio app.
  • Acessa repetidamente ClipData de um app específico. O aviso só aparece quando seu app acessa os dados desse app pela primeira vez.
  • Recuperar metadados para o objeto de transferência, por exemplo, chamando getPrimaryClipDescription(), em vez de getPrimaryClip().

Usar provedores de conteúdo para copiar dados complexos

Os provedores de conteúdo são compatíveis com a cópia de dados complexos, como registros de banco de dados ou fluxos de arquivos. Para copiar os dados, coloque um URI de conteúdo na área de transferência. Os aplicativos de colagem recebem esse URI da área de transferência e o usam para recuperar dados do banco de dados ou descritores de fluxo de arquivos.

Como o aplicativo de colagem só tem o URI de conteúdo dos seus dados, ele precisa saber qual dado recuperar. Para fornecer essas informações, codifique um identificador para os dados no próprio URI ou forneça um URI exclusivo que retorne os dados que você quer copiar. A técnica escolhida depende da organização dos dados.

As seções a seguir descrevem como configurar URIs, fornecer dados complexos e fornecer streams de arquivos. As descrições pressupõem que você esteja familiarizado com os princípios gerais do design de provedores de conteúdo.

Codificar um identificador no URI

Uma técnica útil para copiar dados para a área de transferência com um URI é codificar um identificador para os dados no próprio URI. O provedor de conteúdo pode receber o identificador do URI e usá-lo para recuperar os dados. O aplicativo de colagem não precisa saber que o identificador existe. Ele só precisa receber sua "referência" (o URI mais o identificador) da área de transferência, fornecê-la ao provedor de conteúdo e receber os dados de volta.

Normalmente, um identificador é codificado em um URI de conteúdo ao concatená-lo no fim do URI. Por exemplo, suponha que você definiu seu URI de provedor como a string a seguir:

"content://com.example.contacts"

Caso queira codificar um nome para este URI, use o snippet de código abaixo:

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 você já estiver usando um provedor de conteúdo, adicione um novo caminho de URI que indique que o URI é para cópia. Por exemplo, suponha que você já tenha os seguintes caminhos de URI:

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

É possível adicionar outro caminho para copiar URIs:

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

Em seguida, é possível detectar um URI de cópia por correspondência de padrão e processá-lo com um código específico para copiar e colar.

Em geral, a técnica de codificação é usada se você já usa um provedor de conteúdo, um banco de dados interno ou uma tabela interna para organizar seus dados. Nesses casos, você tem vários dados que quer copiar e, provavelmente, um identificador exclusivo para cada um. Em resposta a uma consulta do aplicativo de colagem, é possível procurar os dados pelo identificador e retorná-los.

Se você não tiver vários dados, provavelmente não precisará codificar um identificador. Você pode usar um URI exclusivo para seu provedor. Em resposta a uma consulta, o provedor retorna os dados que ele contém no momento.

Copiar estruturas de dados

Configure um provedor de conteúdo para copiar e colar dados complexos como uma subclasse do componente ContentProvider. Codifique o URI que você colocou na área de transferência para que ele aponte para o registro exato que você quer fornecer. Além disso, considere o estado atual do aplicativo:

  • Se você já tiver um provedor de conteúdo, poderá melhorar a funcionalidade dele. Talvez você só precise modificar o método query() para processar URIs de aplicativos que querem colar dados. É recomendável modificar o método para processar um padrão de URI de cópia.
  • Se o aplicativo mantém um banco de dados interno, mova-o para um provedor de conteúdo para facilitar a cópia.
  • Se você não estiver usando um banco de dados, poderá implementar um provedor de conteúdo simples que tem como único objetivo oferecer dados para aplicativos que estão colando da área de transferência.

No provedor de conteúdo, substitua pelo menos os seguintes métodos:

query()
Os aplicativos de colagem presumem que podem acessar seus dados usando esse método com o URI que você colocou na área de transferência. Para oferecer suporte à cópia, faça com que esse método detecte URIs que contenham um caminho de cópia especial. Seu aplicativo pode criar um URI de cópia para colocar na área de transferência, contendo o caminho da ação e um ponteiro para o registro exato que você quer copiar.
getType()
Esse método precisa retornar os tipos MIME dos dados que você pretende copiar. O método newUri() chama getType() para colocar os tipos MIME no novo objeto ClipData.

Os tipos MIME para dados complexos são descritos em Provedores de conteúdo.

Não é necessário ter nenhum dos outros métodos de provedor de conteúdo, como insert() ou update(). Um aplicativo de colagem só precisa receber os tipos MIME compatíveis e copiar os dados do seu provedor. Se você já tem esses métodos, eles não interferem nas operações de copiar.

Os snippets a seguir demonstram como configurar o aplicativo para copiar dados complexos:

  1. Nas constantes globais do aplicativo, declare uma string de URI base e um caminho que identifique as strings de URI que você está usando para copiar dados. Declare também um tipo MIME para os dados copiados.

    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. Na atividade da qual os usuários copiam dados, configure o código para copiar dados para a área de transferência. Em resposta a uma solicitação de cópia, coloque o URI na área de transferência.

    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. No escopo global do provedor de conteúdo, crie um correspondente de URI e adicione um padrão de URI que corresponda aos URIs que você colocar na área de transferência.

    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. Configure o método query(). Esse método pode processar diferentes padrões de URI, dependendo de como você o codifica, mas apenas o padrão para a operação de cópia da área de transferência é mostrado.

    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. Configure o método getType() para retornar um tipo MIME adequado para os dados copiados:

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

A seção Colar dados de um URI de conteúdo descreve como receber um URI de conteúdo da área de transferência e usá-lo para receber e colar dados.

Copiar fluxos de dados

Você pode copiar e colar grandes quantidades de texto e dados binários como fluxos. Os dados podem ter formas como as seguintes:

  • Arquivos armazenados no dispositivo
  • Streams de soquetes
  • Grandes quantidades de dados armazenados no sistema de banco de dados subjacente de um provedor

Um provedor de conteúdo para streams de dados fornece acesso aos dados com um objeto descritor do arquivo, como AssetFileDescriptor, em vez de um objeto Cursor. O aplicativo de colagem lê o fluxo de dados usando esse descritor do arquivo.

Para configurar o aplicativo para copiar um fluxo de dados com um provedor, siga estas etapas:

  1. Configure um URI de conteúdo para o fluxo de dados que será colocado na área de transferência. Veja algumas opções de como fazer isso a seguir:
    • Codifique um identificador para o fluxo de dados no URI, conforme descrito na seção Codificar um identificador no URI, e mantenha uma tabela no provedor que contenha identificadores e o nome do fluxo correspondente.
    • Codifique o nome do stream diretamente no URI.
    • Use um URI exclusivo que sempre retorne o fluxo atual do provedor. Se usar essa opção, lembre-se de atualizar seu provedor para apontá-lo a um fluxo diferente sempre que copiar o fluxo para a área de transferência usando o URI.
  2. Forneça um tipo MIME para cada tipo de fluxo de dados que você pretende oferecer. Os aplicativos de colagem precisam dessas informações para determinar se podem colar os dados na área de transferência.
  3. Implemente um dos métodos ContentProvider que retornam um descritor do arquivo para um stream. Se você codificar identificadores no URI de conteúdo, use esse método para determinar qual stream abrir.
  4. Para copiar o fluxo de dados para a área de transferência, construa o URI de conteúdo e coloque-o na área de transferência.

Para colar um fluxo de dados, um aplicativo recebe o clipe da área de transferência, recebe o URI e o usa em uma chamada para um método de descritor do arquivo ContentResolver que abre o fluxo. O método ContentResolver chama o método ContentProvider correspondente, transmitindo o URI de conteúdo. O provedor retorna o descritor do arquivo para o método ContentResolver. O aplicativo de colagem é responsável por ler os dados do stream.

A lista a seguir mostra os métodos mais importantes do descritor de arquivo de um provedor de conteúdo. Cada um deles tem um método ContentResolver correspondente com a string "Descriptor" anexada ao nome do método. Por exemplo, o análogo ContentResolver de openAssetFile() é openAssetFileDescriptor().

openTypedAssetFile()

Esse método retorna um descritor do arquivo de recurso, mas apenas se o Tipo MIME fornecido for compatível com o provedor. O autor da chamada, ou seja, o aplicativo que está fazendo a colagem, fornece um padrão de tipo MIME. O provedor de conteúdo do aplicativo que copia um URI para a área de transferência vai retornar um identificador de arquivo AssetFileDescriptor se puder fornecer esse tipo MIME e vai gerar uma exceção se não puder.

Esse método lida com subseções de arquivos. Você pode usá-lo para ler recursos que o provedor de conteúdo copiou para a área de transferência.

openAssetFile()
Esse método é uma forma mais geral de openTypedAssetFile(). Ele não filtra os tipos MIME permitidos, mas pode ler subseções de arquivos.
openFile()
Essa é uma forma mais geral de openAssetFile(). Ele não pode ler subseções de arquivos.

Se quiser, use o método openPipeHelper() com o método do descritor do arquivo. Isso permite que o aplicativo de colagem leia os dados do stream em uma linha de execução em segundo plano usando um pipe. Para usar esse método, implemente a interface ContentProvider.PipeDataWriter.

Projetar uma funcionalidade eficaz de copiar e colar

Para criar uma funcionalidade de copiar e colar eficaz para seu aplicativo, lembre-se destes pontos:

  • A qualquer momento, há apenas uma transferência na área de transferência. Uma nova operação de cópia por qualquer aplicativo no sistema substitui o clipe anterior. Como o usuário pode sair do aplicativo e copiar antes de retornar, não é possível presumir que a área de transferência contém o clipe que o usuário copiou anteriormente no seu aplicativo.
  • A finalidade de vários objetos ClipData.Item por clipe é permitir copiar e colar várias seleções, em vez de diferentes formas de referência a uma única seleção. Normalmente, todos os objetos ClipData.Item em um clipe precisam ter a mesma forma. Ou seja, todos precisam ser texto simples, URI de conteúdo ou Intent, sem misturas.
  • Ao fornecer dados, você pode oferecer diferentes representações MIME. Adicione os tipos MIME compatíveis ao ClipDescription e, em seguida, implemente os tipos MIME no provedor de conteúdo.
  • Quando você recebe dados da área de transferência, seu aplicativo é responsável por verificar os tipos MIME disponíveis e decidir qual deles usar. Mesmo que haja um clipe na área de transferência e o usuário solicite uma colagem, o aplicativo não é obrigado a fazer isso. Cole se o tipo MIME for compatível. É possível forçar os dados na área de transferência para texto usando coerceToText(). Se o aplicativo for compatível com mais de um dos tipos MIME disponíveis, você poderá permitir que o usuário escolha qual usar.