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

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

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

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

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

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

Как это работает

Вставка изображения клавиатуры требует участия IME и приложения. Следующая последовательность описывает каждый шаг процесса вставки изображения:

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

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

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

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

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

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

Чтобы принимать расширенный контент от IME, приложение должно сообщить IME, какие типы контента оно принимает, и указать метод обратного вызова, который выполняется при получении контента. В следующем примере показано, как создать EditText , который принимает изображения PNG:

Котлин

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
    }
}

Ява

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 контента.

  • createWrapper() оборачивает InputConnection , измененный EditorInfo и обратный вызов в новый InputConnection и возвращает его.

Ниже приведены рекомендуемые практики:

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

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

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

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

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

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

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

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

  • Переопределите onStartInput() или onStartInputView() и прочитайте список поддерживаемых типов контента из целевого редактора. В следующем фрагменте кода показано, как проверить, принимает ли целевой редактор изображения GIF.

Котлин

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.
    }
}

Ява

@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.

Котлин

// 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)
}

Ява

// 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 . Затем ваш IME может предоставить разрешения на доступ к 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() , но вы можете реализовать свой собственный механизм, удовлетворяющий вашим требованиям.

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