
Figura 1: Ejemplo de compatibilidad con teclado de imagen
Con frecuencia, los usuarios quieren comunicarse con 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, por su sigla en inglés) solo podían enviar emojis Unicode a las apps. Para contenido enriquecido, las apps tenían que compilar API específicas que no se podían usar en otras aplicaciones, o bien usar una solución alternativa como enviar imágenes a través de una acción para compartir fácilmente o el portapapeles.
Con 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 demás 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 se ejecuta en dispositivos a partir de Android 3.2 (nivel de API 13) y contiene métodos auxiliares que simplifican la implementación.
Con esta API, puedes compilar apps de mensajería que acepten contenido enriquecido desde cualquier teclado, así como teclados que envíen contenido enriquecido a cualquier app. El Teclado de Google y otras apps, como Mensajes de Google, admiten la API de Commit Content en Android 7.1 (consulta la figura 1).
En esta página, se muestra cómo implementar la API de Commit Content tanto en IME como en apps.
Cómo funciona
La inserción de imágenes en el teclado requiere la participación tanto del IME como de la app. La siguiente secuencia describe cada paso del proceso de inserción de imágenes:
Cuando el usuario presiona un
EditText
, el editor envía una lista de tipos de contenido de MIME, que acepta enEditorInfo.contentMimeTypes
.El IME lee la lista de los tipos compatibles y muestra el contenido en el teclado en pantalla que puede aceptar el editor.
Cuando el usuario selecciona una imagen, el IME llama a
commitContent()
y envía unInputContentInfo
al editor. La llamada decommitContent()
es análoga a la decommitText()
, pero es para contenido enriquecido.InputContentInfo
contiene un URI que identifica el contenido en un proveedor de contenido.
Cómo hacer que las apps admitan imágenes
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 en formato 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); } };
Se producen varios procesos, así que lo explicaremos detalladamente.
En este ejemplo, se usa la biblioteca de compatibilidad, por lo que hay algunas referencias a
android.support.v13.view.inputmethod
en lugar de aandroid.view.inputmethod
.En este ejemplo, se crea un
EditText
y se anula su métodoonCreateInputConnection(EditorInfo)
para modificar laInputConnection
.InputConnection
es el canal de comunicación entre un IME y la app que recibe su entrada.El objeto
super.onCreateInputConnection()
de la llamada conserva el comportamiento integrado (enviar y recibir texto) y te da una referencia aInputConnection
.setContentMimeTypes()
agrega una lista de los tipos de MIME compatibles enEditorInfo
. Asegúrate de llamar asuper.onCreateInputConnection()
antes desetContentMimeTypes()
.callback
se ejecuta cada vez que el IME confirma el contenido. El métodoonCommitContent()
tiene una referencia aInputContentInfoCompat
, que contiene un URI de contenido.- Debes solicitar y actualizar permisos si tu app se ejecuta en API nivel 25 o superior y el IME configuró la marca
INPUT_CONTENT_GRANT_READ_URI_PERMISSION
. De lo contrario, ya deberías tener acceso al URI de contenido, ya sea porque se lo otorgó el IME o porque el proveedor de contenido no restringe el acceso. Para obtener más información, consulta Cómo agregar compatibilidad con imágenes a los IME.
- Debes solicitar y actualizar permisos si tu app se ejecuta en API nivel 25 o superior y el IME configuró la marca
createWrapper()
une elInputConnection
, elEditorInfo
modificado y la devolución de llamada en un nuevoInputConnection
que muestra posteriormente.
Estas son algunas prácticas recomendadas:
Los editores que no admiten contenido enriquecido no deben llamar a
setContentMimeTypes()
y dejar suEditorInfo.contentMimeTypes
configurado ennull
.Los editores deben ignorar el contenido si el tipo de MIME especificado en
InputContentInfo
no coincide con ninguno de los tipos que acepta.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 mostrartrue
de forma asincrónica, incluso antes de subir el contenido.A diferencia del texto que se puede editar en el IME antes de confirmarlo, el contenido enriquecido se confirma inmediatamente. Ten en cuenta que si quieres ofrecer a los usuarios la posibilidad de editar o borrar contenido, debes implementar 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 hacer que los IME admitan imágenes
Los IME que quieren enviar contenido enriquecido a apps deben implementar la API de Commit Content como se muestra a continuación:
- Anula
onStartInput()
oonStartInputView()
, 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 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 } }
- Confirma el contenido de la app cuando el usuario seleccione una imagen. Evita llamar a
commitContent()
cuando se esté redactando texto, ya que eso podría generar 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 descubrir cómo compilar proveedores de contenido, consulta la documentación Proveedor de contenido y Proveedor de archivo.Si estás compilando tu propio proveedor de contenido, te recomendamos que no lo exportes (configura android:exported como
false
). En su lugar, habilita la concesión de permisos en el proveedor estableciendo android:grantUriPermission comotrue
. 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()
, establece el parámetro de marca comoINPUT_CONTENT_GRANT_READ_URI_PERMISSION
. Entonces, el objetoInputContentInfo
que reciba la app podrá solicitar y otorgar permisos de lectura temporales, para lo cual llamará arequestPermission()
yreleasePermission()
.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 manualmente al contenido. Una forma de hacerlo es congrantUriPermission()
, pero puedes implementar un mecanismo propio que satisfaga tus requisitos particulares.
Para probar el 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.