Поддержка клавиатуры с изображениями

Пользователи часто хотят общаться с помощью эмодзи, стикеров и других видов мультимедийного контента. В предыдущих версиях Android экранные клавиатуры, также известные как редакторы методов ввода (IME), могли отправлять в приложения только эмодзи Unicode. Для передачи расширенного контента разработчики создавали специальные API, которые нельзя было использовать в других приложениях, или применяли обходные пути, например отправляли изображения через простое действие по отправке или буфер обмена.

Изображение клавиатуры, поддерживающей поиск изображений
Рисунок 1. Пример поддержки клавиатуры с изображениями.

Начиная с Android 7.1 (уровень API 25), Android SDK включает Commit Content API, который предоставляет универсальный способ для IME отправлять изображения и другой расширенный контент непосредственно в текстовый редактор в приложении. API также доступен в библиотеке поддержки v13, начиная с версии 25.0.0. Мы рекомендуем использовать библиотеку поддержки, поскольку в ней есть вспомогательные методы, упрощающие реализацию.

С помощью этого API вы можете создавать приложения для обмена сообщениями, которые принимают расширенный контент с любой клавиатуры, а также клавиатуры, которые могут отправлять расширенный контент в любое приложение. Google Клавиатура и такие приложения, как Google Сообщения, поддерживают Commit Content API в Android 7.1, как показано на рисунке 1.

В этом документе рассказывается, как реализовать Commit Content API в редакторах методов ввода и приложениях.

Принцип работы

Для вставки изображений с клавиатуры требуется участие редактора метода ввода и приложения. Ниже описаны все этапы этого процесса.

  1. Когда пользователь нажимает на EditText, редактор отправляет список MIME-типов контента, которые он принимает, в EditorInfo.contentMimeTypes.

  2. Редактор метода ввода считывает список поддерживаемых типов и отображает на экранной клавиатуре контент, который может принять редактор.

  3. Когда пользователь выбирает изображение, IME вызывает commitContent() и отправляет InputContentInfo в редактор. Вызов commitContent() аналогичен вызову commitText(), но предназначен для расширенных сведений о товаре. InputContentInfo содержит URI, который идентифицирует контент в поставщике контента.

Этот процесс показан на рисунке 2:

Изображение, на котором показана последовательность от приложения к IME и обратно к приложению
Рисунок 2. Схема передачи данных между приложением и редактором метода ввода.

Добавьте поддержку изображений в приложения

Чтобы принимать расширенный контент от редакторов методов ввода, приложение должно сообщать им, какие типы контента оно принимает, и указывать метод обратного вызова, который выполняется при получении контента. В примере ниже показано, как создать EditText, который принимает изображения 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;
    }
};

Ниже приведены дополнительные пояснения.

  • В этом примере используется библиотека поддержки, поэтому в нем есть ссылки на android.support.v13.view.inputmethod вместо android.view.inputmethod.

  • В этом примере создается объект EditText и переопределяется его метод onCreateInputConnection(EditorInfo), чтобы изменить InputConnection. InputConnection – это канал связи между IME и приложением, которое получает входные данные.

  • Вызов super.onCreateInputConnection() сохраняет встроенное поведение (отправку и получение текста) и предоставляет ссылку на InputConnection.

  • setContentMimeTypes() добавляет список поддерживаемых MIME-типов в EditorInfo. Позвоните по номеру super.onCreateInputConnection() до setContentMimeTypes().

  • callback выполняется каждый раз, когда IME фиксирует контент. Метод onCommitContent() содержит ссылку на InputContentInfoCompat, в котором есть URI контента.

    • Запрашивайте и предоставляйте разрешения, если приложение работает на уровне API 25 или выше и IME устанавливает флаг INPUT_CONTENT_GRANT_READ_URI_PERMISSION. В противном случае у вас уже есть доступ к URI контента, поскольку он предоставлен IME или поставщик контента не ограничивает доступ. Подробнее о том, как добавить поддержку изображений в IME…
  • createWrapper() упаковывает InputConnection, измененный EditorInfo и обратный вызов в новый InputConnection и возвращает его.

Рекомендуем сделать следующее:

  • Редакторы, которые не поддерживают расширенные сведения о товаре, не вызывают setContentMimeTypes() и оставляют для EditorInfo.contentMimeTypes значение null.

  • Редакторы игнорируют контент, если MIME-тип, указанный в InputContentInfo, не соответствует ни одному из типов, которые они принимают.

  • Положение текстового курсора не влияет на расширенные сведения о товаре и не зависит от них. Редакторы могут игнорировать положение курсора при работе с контентом.

  • В методе OnCommitContentListener.onCommitContent() редактора можно асинхронно возвращать true, даже до загрузки контента.

  • В отличие от текста, который можно редактировать в IME перед отправкой, расширенный контент отправляется сразу. Если вы хотите, чтобы пользователи могли редактировать или удалять контент, реализуйте эту логику самостоятельно.

Чтобы протестировать приложение, убедитесь, что на вашем устройстве или эмуляторе есть клавиатура, которая может отправлять расширенный контент. Google Клавиатура доступна на устройствах с Android 7.1 и более поздними версиями ОС.

Добавьте поддержку изображений в редакторы методов ввода

Редакторы методов ввода, которые должны отправлять расширенный контент в приложения, должны реализовывать Commit Content API, как показано в следующем примере:

  • Переопределите onStartInput() или onStartInputView() и прочитайте список поддерживаемых типов контента из целевого редактора. В следующем фрагменте кода показано, как проверить, принимает ли целевой редактор изображения 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.
    }
}
  • Передайте контент в приложение, когда пользователь выберет изображение. Не вызывайте commitContent(), когда пользователь набирает текст, иначе фокус может сместиться с редактора. В следующем фрагменте кода показано, как зафиксировать 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);
}

Как разработчику IME, вам, скорее всего, потребуется реализовать собственный поставщик контента, чтобы отвечать на запросы URI контента. Исключение составляют случаи, когда ваш IME поддерживает контент от существующих поставщиков контента, например MediaStore. Информацию о создании поставщиков контента можно найти в документации по поставщикам контента и поставщикам файлов.

Если вы создаете собственный поставщик контента, рекомендуем не экспортировать его, задав для атрибута android:exported значение false. Вместо этого включите предоставление разрешений в поставщике, установив для android:grantUriPermission значение true. Затем редактор метода ввода может предоставить разрешения на доступ к URI контента, когда контент будет зафиксирован. Сделать это можно двумя способами:

  • На устройствах с Android 7.1 (уровень API 25) и более поздними версиями при вызове commitContent() установите для параметра flag значение INPUT_CONTENT_GRANT_READ_URI_PERMISSION. Затем объект InputContentInfo, который получает приложение, может запрашивать и отменять временные разрешения на чтение, вызывая методы requestPermission() и releasePermission().

  • В Android 7.0 (уровень API 24) и более ранних версиях INPUT_CONTENT_GRANT_READ_URI_PERMISSION игнорируется, поэтому разрешение на доступ к контенту нужно предоставлять вручную. Один из способов сделать это – использовать grantUriPermission(), но вы можете реализовать собственный механизм, отвечающий вашим требованиям.

Чтобы протестировать редактор метода ввода, убедитесь, что на вашем устройстве или эмуляторе есть приложение, которое может получать расширенный контент. Приложение Google Messenger можно использовать на устройствах с Android 7.1 и более поздними версиями.