Compatibilidade com teclado de imagens

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:

  1. Quando o usuário toca em um EditText, o editor envia uma lista de tipos de conteúdo MIME que são aceitos em EditorInfo.contentMimeTypes.

  2. O IME lê a lista de tipos compatíveis e exibe o conteúdo no teclado de software que o editor pode aceitar.

  3. Quando o usuário seleciona uma imagem, o IME chama commitContent() e envia um InputContentInfo para o editor. A chamada commitContent() é análoga à chamada commitText(), mas para conteúdo avançado. O InputContentInfo 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.

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 o EditorInfo.contentMimeTypes configurado como null.

  • 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 retornar true 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() ou onStartInputView() 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 como true. 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:

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.