Compatibilidad con teclado de imagen

Con frecuencia, los usuarios quieren comunicarse mediante emojis, calcomanías y otros tipos de contenido enriquecido. En versiones anteriores de Android, los teclados en pantalla, también conocidos como editores de método de entrada, o IME, solo podían enviar emojis Unicode a las apps. Para el contenido enriquecido, las apps compilaron APIs específicas de la app que no se podían usar en otras apps o usaban soluciones alternativas, como el envío de imágenes mediante una acción simple para compartir o el portapapeles.

Imagen de un teclado que admite la búsqueda de imágenes
Figura 1: Ejemplo de compatibilidad con teclados de imágenes.

A partir de Android 7.1 (nivel de API 25), el SDK de Android incluye la API de Commit Content, que proporciona una manera universal para que los IME envíen imágenes y otro contenido enriquecido directamente al editor de texto de una app. La API también está disponible en la biblioteca de compatibilidad v13 a partir de la revisión 25.0.0. Recomendamos usar la biblioteca de compatibilidad porque contiene métodos auxiliares que simplifican la implementación.

Con esta API, puedes compilar apps de mensajería que acepten contenido enriquecido de cualquier teclado, así como teclados que puedan enviar contenido enriquecido a cualquier app. El teclado de Google y las apps como Messages by Google admiten la API de Commit Content en Android 7.1, como se muestra en la Figura 1.

En este documento, se muestra cómo implementar la API de Commit Content en los IME y en las apps.

Cómo funciona

La inserción de imágenes en el teclado requiere la participación del IME y la app. En la siguiente secuencia, se describe cada paso del proceso de inserción de imágenes:

  1. Cuando el usuario presiona un EditText, el editor envía una lista de tipos de contenido de MIME que acepta en EditorInfo.contentMimeTypes.

  2. El IME lee la lista de los tipos compatibles y muestra el contenido en el teclado en pantalla que puede aceptar el editor.

  3. Cuando el usuario selecciona una imagen, el IME llama a commitContent() y envía un InputContentInfo al editor. La llamada commitContent() es análoga a la llamada commitText(), pero para contenido enriquecido. InputContentInfo contiene un URI que identifica el contenido en un proveedor de contenido.

Este proceso se muestra en la figura 2:

Una imagen que muestra la secuencia desde Aplicación hasta IME y de vuelta a Aplicación
Figura 2: Aplicación al IME al flujo de aplicaciones

Cómo agregar compatibilidad con imágenes a las apps

Para aceptar contenido enriquecido de los IME, las apps deben indicarles qué tipos de contenido aceptan y especificar un método de devolución de llamada que se ejecute cuando se reciba contenido. En el siguiente ejemplo, se muestra cómo crear un EditText que acepte imágenes 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;
    }
};

A continuación, se incluye una explicación más detallada:

Las siguientes son prácticas recomendadas:

  • Los editores que no admiten contenido enriquecido no llaman a setContentMimeTypes() y dejan su EditorInfo.contentMimeTypes establecido en null.

  • Los editores ignoran el contenido si el tipo de MIME especificado en InputContentInfo no coincide con ninguno de los tipos que aceptan.

  • El contenido enriquecido no afecta la posición del cursor de texto y tampoco se ve afectado por ella. Los editores pueden ignorar la posición del cursor cuando trabajan con contenido.

  • En el método OnCommitContentListener.onCommitContent() del editor, puedes mostrar true de forma asíncrona, incluso antes de cargar el contenido.

  • A diferencia del texto, que se puede editar en el IME antes de confirmarlo, el contenido enriquecido se confirma de inmediato. Si quieres permitir que los usuarios editen o borren contenido, implementa la lógica tú mismo.

Para probar tu app, asegúrate de que tu dispositivo o emulador tengan un teclado que pueda enviar contenido enriquecido. Puedes usar el Teclado de Google en Android 7.1 o versiones posteriores.

Cómo agregar compatibilidad con imágenes a los IME

Los IME que quieren enviar contenido enriquecido a apps deben implementar la API de Commit Content, como se muestra en el siguiente ejemplo:

  • Anula onStartInput() o onStartInputView(), y lee la lista de tipos de contenido compatibles desde el editor de destino. En el siguiente fragmento de código, se muestra cómo verificar si el editor de destino acepta imágenes 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.
    }
}

  • Confirma el contenido de la app cuando el usuario seleccione una imagen. Evita llamar a commitContent() cuando se esté redactando texto, ya que esto podría provocar que el editor pierda el foco. En el siguiente fragmento de código, se muestra cómo confirmar una imagen 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 IME, lo más probable es que tengas que implementar tu propio proveedor de contenido para responder a las solicitudes de URI de contenido. La excepción es si tu IME admite contenido de proveedores existentes como MediaStore. Para obtener información sobre cómo compilar proveedores de contenido, consulta la documentación sobre el proveedor de contenido y el proveedor de archivos.

Si estás compilando tu propio proveedor de contenido, te recomendamos que no lo exportes configurando android:exported como false. En su lugar, habilita la concesión de permisos en el proveedor estableciendo android:grantUriPermission en true. Luego, tu IME puede otorgar permisos para acceder al URI de contenido cuando se confirme el contenido. Existen dos maneras de hacerlo:

  • En Android 7.1 (nivel de API 25) y versiones posteriores, cuando llames a commitContent(), configura el parámetro de marca en INPUT_CONTENT_GRANT_READ_URI_PERMISSION. Luego, el objeto InputContentInfo que recibe la app puede solicitar y liberar permisos de lectura temporales llamando a requestPermission() y releasePermission().

  • En Android 7.0 (nivel de API 24) y versiones anteriores, se ignora INPUT_CONTENT_GRANT_READ_URI_PERMISSION, por lo que debes otorgar permiso al contenido de forma manual. Una forma de hacerlo es con grantUriPermission(), pero puedes implementar un mecanismo propio que cumpla con tus requisitos.

Para probar tu IME, asegúrate de que tu dispositivo o emulador tengan una app que pueda recibir contenido enriquecido. Puedes usar la app de Mensajes de Google en Android 7.1 o versiones posteriores.