
Figura 1. Exemplo de suporte a teclados de imagem.
Os usuários costumam querer se comunicar com emojis, adesivos e outros tipos de conteúdo avançado. Nas versões anteriores do Android, os teclados virtuais (também conhecidos como editores de método de entrada ou IMEs, na sigla em inglês) podiam enviar apenas emojis do Unicode para apps. Para conteúdo avançado, os apps precisavam criar APIs específicas que não podiam ser usadas em outros apps ou usar uma solução alternativa, como enviar imagens pela ação de compartilhamento fácil ou área de transferência.
Com o Android 7.1 (API de nível 25), o SDK Android inclui a API Commit Content, que oferece uma maneira universal para os IMEs enviarem imagens e outros conteúdos avançados diretamente para um editor de texto em um app. A API também está disponível na Biblioteca de Suporte v13 a partir da revisão 25.0.0. Recomendamos usar a Biblioteca de Suporte, porque ela pode ser executada em dispositivos a partir do Android 3.2 (nível 13 da API) e contém métodos auxiliares que simplificam a implementação.
Com essa API, é possível criar apps de mensagens que aceitam conteúdo avançado de qualquer teclado, bem como teclados que podem enviar conteúdo avançado para qualquer app. O Teclado do Google e apps como o Mensagens oferecem suporte à API Commit Content no Android 7.1 (veja a figura 1).
Esta página mostra como implementar a API Commit Content em IMEs e apps.
Como funciona
A inserção de imagem do teclado exige a participação do IME e do app. A sequência a seguir descreve cada etapa do processo de inserção de imagem:
Quando o usuário toca em um
EditText
, o editor envia uma lista de tipos de conteúdo MIME que são aceitos emEditorInfo.contentMimeTypes
.O IME lê a lista de tipos compatíveis e exibe o conteúdo no teclado de software que o editor pode aceitar.
Quando o usuário seleciona uma imagem, o IME chama
commitContent()
e envia umInputContentInfo
para o editor. A chamadacommitContent()
é análoga à chamadacommitText()
, mas para conteúdo avançado. OInputContentInfo
contém um URI que identifica o conteúdo em um provedor de conteúdo.
Adicionar compatibilidade com imagens a apps
Para aceitar conteúdo avançado de IMEs, um app precisa os informar sobre quais tipos de conteúdo
são aceitos e especificar um método de callback que seja executado quando o conteúdo for
recebido. O exemplo a seguir demonstra como criar um
EditText
que aceite imagens PNG:
Kotlin
val editText = object : EditText(this) { override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { val ic: InputConnection = super.onCreateInputConnection(editorInfo) EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/png")) val callback = InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts -> val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 // read and display inputContentInfo asynchronously if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { try { inputContentInfo.requestPermission() } catch (e: Exception) { return@OnCommitContentListener false // return false if failed } } // read and display inputContentInfo asynchronously. // call inputContentInfo.releasePermission() as needed. true // return true if succeeded } return InputConnectionCompat.createWrapper(ic, editorInfo, callback) } }
Java
EditText editText = new EditText(this) { @Override public InputConnection onCreateInputConnection(EditorInfo editorInfo) { final InputConnection ic = super.onCreateInputConnection(editorInfo); EditorInfoCompat.setContentMimeTypes(editorInfo, new String [] {"image/png"}); final InputConnectionCompat.OnCommitContentListener callback = new InputConnectionCompat.OnCommitContentListener() { @Override public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { // read and display inputContentInfo asynchronously if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { try { inputContentInfo.requestPermission(); } catch (Exception e) { return false; // return false if failed } } // read and display inputContentInfo asynchronously. // call inputContentInfo.releasePermission() as needed. return true; // return true if succeeded } }; return InputConnectionCompat.createWrapper(ic, editorInfo, callback); } };
Muita coisa está acontecendo, então, vamos explicar.
Este exemplo usa a Biblioteca de Suporte, portanto, há algumas referências a
android.support.v13.view.inputmethod
, em vez deandroid.view.inputmethod
.Este exemplo cria um
EditText
e substitui o métodoonCreateInputConnection(EditorInfo)
dele para modificar aInputConnection
. AInputConnection
é o canal de comunicação entre um IME e o app que está recebendo a entrada.A chamada
super.onCreateInputConnection()
preserva o comportamento integrado (envio e recebimento de texto) e fornece uma referência àInputConnection
.setContentMimeTypes()
adiciona aoEditorInfo
uma lista de tipos MIME que tem suporte. Chamesuper.onCreateInputConnection()
antes desetContentMimeTypes()
.callback
é executado sempre que o IME confirma conteúdo. O métodoonCommitContent()
tem uma referência aInputContentInfoCompat
, que contém um URI de conteúdo.- Você precisa solicitar e liberar permissões caso seu app esteja sendo executado na API
de nível 25 ou versões mais recentes e a sinalização
INPUT_CONTENT_GRANT_READ_URI_PERMISSION
tenha sido definida pelo IME. Caso contrário, você já terá acesso ao URI do conteúdo, porque ele foi concedido pelo IME ou porque o provedor de conteúdo não restringe o acesso. Para mais informações, consulte Adicionar suporte a imagens a IMEs.
- Você precisa solicitar e liberar permissões caso seu app esteja sendo executado na API
de nível 25 ou versões mais recentes e a sinalização
createWrapper()
agrupa aInputConnection
, oEditorInfo
modificado e o callback em uma novaInputConnection
, que é retornada.
Veja algumas práticas recomendadas:
Editores que não são compatíveis com conteúdo avançado não podem chamar
setContentMimeTypes()
e deixar oEditorInfo.contentMimeTypes
configurado comonull
.Os editores precisarão ignorar o conteúdo se o tipo MIME especificado no
InputContentInfo
não corresponder a nenhum dos tipos aceitos.O conteúdo avançado não afeta e não é afetado pela posição do cursor de texto. Os editores podem ignorar a posição do cursor ao trabalhar com o conteúdo.
No método
OnCommitContentListener.onCommitContent()
do editor, é possível retornartrue
de forma assíncrona, mesmo antes de carregar o conteúdo.Diferentemente do texto que pode ser editado no IME antes de ser confirmado, o conteúdo avançado é confirmado imediatamente. Se quiser fornecer aos usuários a possibilidade de editar ou excluir conteúdo, você mesmo precisará implementar a lógica.
Para testar seu app, verifique se seu dispositivo ou emulador tem um teclado capaz de enviar conteúdo avançado. Você pode usar o Teclado do Google no Android 7.1 ou versões mais recentes.
Adicionar suporte de imagens a IMEs
Os IMEs que queiram enviar conteúdo avançado para apps precisam implementar a API Commit Content, conforme mostrado abaixo:
- Substitua
onStartInput()
ouonStartInputView()
e leia a lista de tipos de conteúdo compatíveis com o editor de destino. O snippet de código a seguir mostra como verificar se o editor de destino aceita imagens GIF.
Kotlin
override fun onStartInputView(editorInfo: EditorInfo, restarting: Boolean) { val mimeTypes: Array<String> = EditorInfoCompat.getContentMimeTypes(editorInfo) val gifSupported: Boolean = mimeTypes.any { ClipDescription.compareMimeTypes(it, "image/gif") } if (gifSupported) { // the target editor supports GIFs. enable corresponding content } else { // the target editor does not support GIFs. disable corresponding content } }
Java
@Override public void onStartInputView(EditorInfo info, boolean restarting) { String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo); boolean gifSupported = false; for (String mimeType : mimeTypes) { if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) { gifSupported = true; } } if (gifSupported) { // the target editor supports GIFs. enable corresponding content } else { // the target editor does not support GIFs. disable corresponding content } }
- Confirme o conteúdo do app quando os usuários selecionarem uma imagem. Evite chamar
commitContent()
quando houver algum texto sendo escrito, porque isso pode fazer com que o editor perca o foco. O snippet de código a seguir mostra como confirmar uma imagem GIF.
Kotlin
/** * Commits a GIF image * * @param contentUri Content URI of the GIF image to be sent * @param imageDescription Description of the GIF image to be sent */ fun commitGifImage(contentUri: Uri, imageDescription: String) { val inputContentInfo = InputContentInfoCompat( contentUri, ClipDescription(imageDescription, arrayOf("image/gif")), null ) val inputConnection = currentInputConnection val editorInfo = currentInputEditorInfo var flags = 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION } InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null) }
Java
/** * Commits a GIF image * * @param contentUri Content URI of the GIF image to be sent * @param imageDescription Description of the GIF image to be sent */ public static void commitGifImage(Uri contentUri, String imageDescription) { InputContentInfoCompat inputContentInfo = new InputContentInfoCompat( contentUri, new ClipDescription(imageDescription, new String[]{"image/gif"}), null ); InputConnection inputConnection = getCurrentInputConnection(); EditorInfo editorInfo = getCurrentInputEditorInfo(); Int flags = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; } InputConnectionCompat.commitContent( inputConnection, editorInfo, inputContentInfo, flags, null); }
Como autor de um IME, você provavelmente precisará implementar seu próprio provedor de conteúdo para responder a solicitações de URI de conteúdo. Isso não vai ser necessário se o IME oferecer suporte a conteúdo de provedores de conteúdo já existentes, como
MediaStore
. Para ver informações sobre como criar provedores de conteúdo, consulte a documentação de Provedor de conteúdo e Provedor de arquivos.Caso esteja criando seu próprio provedor de conteúdo, recomendamos não exportá-lo (configure android:exported como
false
). Em vez disso, ative a concessão de permissões no provedor configurando android:grantUriPermission comotrue
. Depois disso, seu IME poderá conceder permissões para acessar o URI de conteúdo quando o conteúdo for confirmado. Há duas maneiras de fazer isso:No Android 7.1 (API de nível 25) ou mais recentes, ao chamar
commitContent()
, defina o parâmetro de sinalização comoINPUT_CONTENT_GRANT_READ_URI_PERMISSION
. Em seguida, o objetoInputContentInfo
recebido pelo app poderá solicitar e liberar permissões de leitura temporárias chamandorequestPermission()
ereleasePermission()
.No Android 7.0 (API de nível 24) ou versões anteriores,
INPUT_CONTENT_GRANT_READ_URI_PERMISSION
é ignorado. Portanto, é necessário conceder permissão ao conteúdo manualmente. Uma forma de fazer isso é comgrantUriPermission()
, mas também é possível implementar um mecanismo próprio que atenda aos seus requisitos.
Para testar seu IME, verifique se o dispositivo ou emulador tem um app capaz de receber conteúdo avançado. Você pode usar o app Google Messenger no Android 7.1 ou versões mais recentes.