Compatibilidade com teclado de imagens

Os usuários geralmente querem se comunicar usando emojis, adesivos e outros tipos de conteúdo avançado. Nas versões anteriores do Android, os teclados de software, também conhecidos como editores de método de entrada (IMEs, na sigla em inglês), podiam enviar apenas emojis Unicode para apps. Para conteúdo avançado, os apps criam APIs específicas que não podem ser usadas em outros apps ou usam soluções alternativas, como enviar imagens com uma ação de compartilhamento simples ou a área de transferência.

Uma imagem que mostra um teclado compatível com a pesquisa por imagens
Figura 1. Exemplo de suporte ao teclado de imagens.

No Android 7.1 (nível 25 da API) e versões mais recentes, o SDK do Android inclui a API Commit Content, que oferece uma maneira universal de IMEs enviarem imagens e outros conteúdo avançado 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 o uso da Biblioteca de Suporte porque ela contém métodos auxiliares que simplificam a implementação.

Com essa API, você pode criar apps de mensagens que aceitam conteúdo avançado de qualquer teclado e teclados que podem enviar conteúdo avançado para qualquer app. O Teclado do Google e apps como o Messages do Google oferecem suporte à API Commit Content no Android 7.1, como mostrado na Figura 1.

Este documento mostra como implementar a API Commit Content em IMEs e apps.

Como funciona

A inserção de imagem do teclado requer 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 uma 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 uma InputContentInfo para o editor. A chamada de commitContent() é análoga à chamada de commitText(), mas para conteúdo avançado. O InputContentInfo contém um URI que identifica o conteúdo em um provedor de conteúdo.

Esse processo está descrito na Figura 2:

Uma imagem mostrando a sequência de Application para IME e de volta para Application
Figura 2. Aplicação ao IME para o fluxo de aplicativo.

Adicionar compatibilidade com imagens a apps

Para aceitar conteúdo avançado de IMEs, um app precisa informar a eles 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

var editText: EditText = object : EditText(this) {
    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        var ic = super.onCreateInputConnection(outAttrs)
        EditorInfoCompat.setContentMimeTypes(outAttrs, arrayOf("image/png"))
        val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this)
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes)
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs)
        }
        return ic
    }
}

Java

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection ic = super.onCreateInputConnection(outAttrs);
        EditorInfoCompat.setContentMimeTypes(outAttrs, new String[]{"image/png"});
        String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
        }
        return ic;
    }
};

Confira a seguir uma explicação mais detalhada:

Confira a seguir as práticas recomendadas:

  • Os editores que não são compatíveis com conteúdo avançado não chamam setContentMimeTypes() e deixam o EditorInfo.contentMimeTypes definido como null.

  • Os editores vão ignorar o conteúdo se o tipo MIME especificado em 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.

  • Ao contrário do texto, que pode ser editado no IME antes de ser confirmado, o conteúdo avançado é confirmado imediatamente. Se você quiser permitir que os usuários editem ou excluam conteúdo, implemente a lógica por conta própria.

Para testar seu app, verifique se o dispositivo ou emulador tem um teclado que possa enviar conteúdo avançado. Você pode usar o Teclado do Google no Android 7.1 ou versões mais recentes.

Adicionar suporte a imagens a IMEs

Os IMEs que quiserem enviar conteúdo avançado para apps precisam implementar a API Commit Content, conforme mostrado no exemplo a seguir:

  • Modifique onStartInput() ou onStartInputView() e leia a lista de tipos de conteúdo com suporte no 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 the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the 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 the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

  • Confirme o conteúdo do app quando o usuário selecionar 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, é provável que você precise implementar seu próprio provedor de conteúdo para responder a solicitações de URI de conteúdo. A exceção é se o IME oferecer suporte a conteúdo de provedores de conteúdo existentes, como MediaStore. Para informações sobre como criar provedores de conteúdo, consulte a documentação do provedor de conteúdo e do provedor de arquivos.

Se você estiver criando seu próprio provedor de conteúdo, recomendamos que não o exporte definindo android:exported como false. Em vez disso, ative a concessão de permissões no provedor, definindo 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:

  • No Android 7.1 (nível 25 da API) e versões mais recentes, ao chamar commitContent(), defina o parâmetro de sinalização como INPUT_CONTENT_GRANT_READ_URI_PERMISSION. Em seguida, o objeto InputContentInfo que o app recebe pode solicitar e liberar permissões de leitura temporárias chamando requestPermission() e releasePermission().

  • No Android 7.0 (nível 24 da API) e versões anteriores, INPUT_CONTENT_GRANT_READ_URI_PERMISSION é ignorado. Portanto, conceda manualmente permissão ao conteúdo. Uma maneira de fazer isso é com grantUriPermission(), mas você pode implementar um mecanismo próprio que atenda aos seus requisitos.

Para testar o IME, verifique se o dispositivo ou emulador tem um app que possa receber conteúdo avançado. Você pode usar o app Google Messenger no Android 7.1 ou versões mais recentes.