Как получать расширенный контент

Рисунок 1. Унифицированный API позволяет обрабатывать входящий контент в одном месте независимо от механизма пользовательского интерфейса, например вставки из меню, которое открывается при нажатии и удержании, или перетаскивания.

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

Вы можете прикрепить интерфейс, например OnReceiveContentListener, к компонентам пользовательского интерфейса и получать обратный вызов, когда контент вставляется с помощью любого механизма. Обратный вызов становится единственным местом, где ваш код обрабатывает получение всего контента, от простого и стилизованного текста до разметки, изображений, видео, аудиофайлов и других.

Для обратной совместимости с предыдущими версиями Android этот API также доступен в AndroidX, начиная с Core 1.7 и Appcompat 1.4. Рекомендуем использовать его при реализации этой функции.

Обзор

В других API каждому механизму интерфейса, например меню, которое открывается при нажатии и удержании, или перетаскиванию, соответствует свой API. Это означает, что вам нужно интегрировать каждый API отдельно, добавляя похожий код для каждого механизма, который вставляет контент:

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

OnReceiveContentListener API объединяет эти пути выполнения в единый API, который вы можете реализовать. Это позволяет вам сосредоточиться на логике приложения, а платформа позаботится обо всем остальном.

Изображение, показывающее упрощенный унифицированный API
Рисунок 3. Унифицированный API позволяет реализовать единый API, поддерживающий все механизмы пользовательского интерфейса.

Кроме того, если на платформу будут добавлены новые способы вставки контента, вам не придется вносить дополнительные изменения в код, чтобы обеспечить их поддержку в приложении. Если же вам нужно полностью настроить приложение для определенного варианта использования, вы по-прежнему можете использовать существующие API, которые работают так же, как и раньше.

Реализация

API представляет собой интерфейс прослушивателя с одним методом – OnReceiveContentListener. Чтобы обеспечить поддержку более ранних версий платформы Android, рекомендуем использовать соответствующий интерфейс OnReceiveContentListener в библиотеке AndroidX Core.

Чтобы использовать API, реализуйте прослушиватель, указав, какие типы контента может обрабатывать ваше приложение:

Kotlin

object MyReceiver : OnReceiveContentListener {
    val MIME_TYPES = arrayOf("image/*", "video/*")
    
    // ...
    
    override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
        TODO("Not yet implemented")
    }
}

Java

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
     // ...
}

Указав все MIME-типы контента, которые поддерживает ваше приложение, реализуйте остальную часть прослушивателя:

Kotlin

class MyReceiver : OnReceiveContentListener {
    override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat {
        val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
        val uriContent = split.first
        val remaining = split.second
        if (uriContent != null) {
            // App-specific logic to handle the URI(s) in uriContent.
        }
        // Return anything that your app didn't handle. This preserves the
        // default platform behavior for text and anything else that you aren't
        // implementing custom handling for.
        return remaining
    }

    companion object {
        val MIME_TYPES = arrayOf("image/*", "video/*")
    }
}

Java

 public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};

     @Override
     public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) {
         Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition(
                 item -> item.getUri() != null);
         ContentInfo uriContent = split.first;
         ContentInfo remaining = split.second;
         if (uriContent != null) {
             // App-specific logic to handle the URI(s) in uriContent.
         }
         // Return anything that your app didn't handle. This preserves the
         // default platform behavior for text and anything else that you aren't
         // implementing custom handling for.
         return remaining;
     }
 }

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

После реализации прослушивателя установите его для соответствующих элементов пользовательского интерфейса в приложении:

Kotlin

class MyActivity : Activity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        val myInput = findViewById(R.id.my_input)
        ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, MyReceiver())
    }
}

Java

public class MyActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // ...

         AppCompatEditText myInput = findViewById(R.id.my_input);
         ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, new MyReceiver());
     }
}

Разрешения URI

Разрешения на чтение предоставляются и отзываются платформой автоматически для любых URI контента в полезной нагрузке, переданной в OnReceiveContentListener.

Обычно приложение обрабатывает URI контента в сервисе или действии. Для длительной обработки используйте WorkManager. При реализации этого подхода расширьте разрешения для целевого сервиса или действия, передав контент с помощью Intent.setClipData и установив флаг FLAG_GRANT_READ_URI_PERMISSION.

Вы также можете использовать фоновый поток в текущем контексте для обработки контента. В этом случае необходимо сохранить ссылку на объект payload, полученный прослушивателем, чтобы разрешения не были отозваны платформой преждевременно.

Специальные элементы интерфейса

Если в вашем приложении используется пользовательский подкласс View, убедитесь, что OnReceiveContentListener не обходится.

Если ваш класс View переопределяет метод onCreateInputConnection, используйте Jetpack API InputConnectionCompat.createWrapper, чтобы настроить InputConnection.

Если класс View переопределяет метод onTextContextMenuItem, делегируйте его суперклассу, когда пункт меню – R.id.paste или R.id.pasteAsPlainText.

Сравнение с API изображений клавиатуры

API OnReceiveContentListener можно рассматривать как следующую версию существующего API изображений клавиатуры. Этот унифицированный API поддерживает функциональность API изображений клавиатуры, а также некоторые дополнительные функции. Совместимость устройств и функций зависит от того, используете ли вы библиотеку Jetpack или нативные API из Android SDK.

Таблица 1. Поддерживаемые функции и уровни API для Jetpack.
Действие или функция Поддерживается API изображений клавиатуры Поддерживается унифицированным API
Как вставить чип с помощью клавиатуры Да (уровень API 13 и выше) Да (уровень API 13 и выше)
Как вставить чип с помощью меню, которое открывается нажатием и удержанием Нет Да
Как вставить объект перетаскиванием Нет Да (уровень API 24 и выше)
Таблица 2. Поддерживаемые функции и уровни API для нативных API.
Действие или функция Поддерживается API изображений клавиатуры Поддерживается унифицированным API
Как вставить чип с помощью клавиатуры Да (уровень API 25 и выше) Да (Android 12 и более поздние версии)
Как вставить чип с помощью меню, которое открывается нажатием и удержанием Нет
Как вставить объект перетаскиванием Нет