Создайте поставщика облачного мультимедиа для Android

Поставщик облачных медиафайлов предоставляет дополнительный облачный медиаконтент средству выбора фотографий Android . Пользователи могут выбирать фотографии или видео, предоставленные поставщиком облачных мультимедиа, когда приложение использует ACTION_PICK_IMAGES или ACTION_GET_CONTENT для запроса медиафайлов у пользователя. Поставщик облачных медиа также может предоставить информацию об альбомах , которую можно просмотреть в средстве выбора фотографий Android.

Прежде чем начать

Прежде чем приступить к созданию поставщика облачных медиа, примите во внимание следующие моменты.

Право на участие

Android запускает пилотную программу, позволяющую приложениям, номинированным OEM-производителями, стать поставщиками облачных медиа. В настоящее время в этой программе могут участвовать только приложения, номинированные OEM-производителями, чтобы стать поставщиками облачных медиа для Android . Каждый OEM-производитель может номинировать до трех приложений. После одобрения эти приложения становятся доступными в качестве поставщиков облачных медиа на любом устройстве GMS под управлением Android, на котором они установлены.

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

Решите, нужно ли вам создавать поставщика облачных медиа

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

Один активный поставщик облачных услуг на каждый профиль

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

По умолчанию средство выбора фотографий Android попытается выбрать поставщика облачных услуг автоматически.

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

  • Если на устройстве имеется более одного подходящего поставщика облачных услуг, и ни один из них не соответствует значению по умолчанию, выбранному OEM-производителем, ни одно приложение не будет выбрано.

Создайте своего поставщика облачных медиа

На следующей диаграмме показана последовательность событий до и во время сеанса выбора фотографий между приложением Android, средством выбора фотографий Android, MediaProvider локального устройства и CloudMediaProvider .

Диаграмма последовательности, показывающая поток от средства выбора фотографий до поставщика облачных медиафайлов.
Рисунок 1: Диаграмма последовательности событий во время сеанса выбора фотографий.
  1. Система инициализирует предпочитаемого пользователем облачного провайдера и периодически синхронизирует метаданные мультимедиа с серверной частью средства выбора фотографий Android.
  2. Когда приложение Android запускает средство выбора фотографий, прежде чем показывать пользователю объединенную сетку локальных или облачных элементов, средство выбора фотографий выполняет чувствительную к задержке инкрементальную синхронизацию с поставщиком облака, чтобы обеспечить максимально актуальную актуальность результатов. После получения ответа или по истечении крайнего срока в сетке выбора фотографий теперь отображаются все доступные фотографии, объединяющие фотографии, хранящиеся локально на вашем устройстве, с фотографиями, синхронизированными из облака.
  3. Пока пользователь прокручивает страницу, средство выбора фотографий получает миниатюры мультимедиа от облачного поставщика мультимедиа для отображения в пользовательском интерфейсе.
  4. Когда пользователь завершает сеанс и результаты включают элемент облачного мультимедиа, средство выбора фотографий запрашивает дескрипторы файлов для содержимого, генерирует URI и предоставляет доступ к файлу вызывающему приложению.
  5. Теперь приложение может открывать URI и имеет доступ только для чтения к содержимому мультимедиа. По умолчанию конфиденциальные метаданные удаляются. Средство выбора фотографий использует файловую систему FUSE для координации обмена данными между приложением Android и поставщиком облачных носителей.

Общие проблемы

Вот несколько важных соображений, которые следует учитывать при рассмотрении реализации:

Избегайте дубликатов файлов

Поскольку средство выбора фотографий Android не имеет возможности проверять состояние облачных носителей, CloudMediaProvider должен предоставить MEDIA_STORE_URI в строке курсора любого файла, который существует как в облаке, так и на локальном устройстве, иначе пользователь увидит дубликаты файлов в подборщик фотографий.

Оптимизация размеров изображений для предварительного просмотра

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

Обеспечьте правильную ориентацию

Если миниатюры, возвращаемые в onOpenPreview не содержат данных EXIF, их следует возвращать в правильной ориентации, чтобы избежать неправильного поворота миниатюр в сетке предварительного просмотра.

Предотвратить несанкционированный доступ

Проверьте MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION перед возвратом данных вызывающей стороне из ContentProvider. Это предотвратит доступ неавторизованных приложений к облачным данным.

Класс CloudMediaProvider.

Класс CloudMediaProvider , производный от android.content.ContentProvider , включает методы, подобные показанным в следующем примере:

Котлин

abstract class CloudMediaProvider : ContentProvider() {

    @NonNull
    abstract override fun onGetMediaCollectionInfo(@NonNull bundle: Bundle): Bundle

    @NonNull
    override fun onQueryAlbums(@NonNull bundle: Bundle): Cursor = TODO("Implement onQueryAlbums")

    @NonNull
    abstract override fun onQueryDeletedMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onQueryMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onOpenMedia(
        @NonNull string: String,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): ParcelFileDescriptor

    @NonNull
    abstract override fun onOpenPreview(
        @NonNull string: String,
        @NonNull point: Point,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): AssetFileDescriptor

    @Nullable
    override fun onCreateCloudMediaSurfaceController(
        @NonNull bundle: Bundle,
        @NonNull callback: CloudMediaSurfaceStateChangedCallback
    ): CloudMediaSurfaceController? = null
}

Ява

public abstract class CloudMediaProvider extends android.content.ContentProvider {

  @NonNull
  public abstract android.os.Bundle onGetMediaCollectionInfo(@NonNull android.os.Bundle);

  @NonNull
  public android.database.Cursor onQueryAlbums(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryDeletedMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.os.ParcelFileDescriptor onOpenMedia(@NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @NonNull
  public abstract android.content.res.AssetFileDescriptor onOpenPreview(@NonNull String, @NonNull android.graphics.Point, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @Nullable
  public android.provider.CloudMediaProvider.CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull android.os.Bundle, @NonNull android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback);
}

Класс CloudMediaProviderContract.

В дополнение к основному классу реализации CloudMediaProvider средство выбора фотографий Android включает класс CloudMediaProviderContract . В этом классе описывается взаимодействие между средством выбора фотографий и поставщиком облачных мультимедиа, включая такие аспекты, как MediaCollectionInfo для операций синхронизации, ожидаемые столбцы Cursor и дополнительные возможности Bundle .

Котлин

object CloudMediaProviderContract {

    const val EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID"
    const val EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED"
    const val EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID"
    const val EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE"
    const val EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN"
    const val EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL"
    const val EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED"
    const val EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION"
    const val MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
    const val PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER"

    object MediaColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DURATION_MILLIS = "duration_millis"
        const val HEIGHT = "height"
        const val ID = "id"
        const val IS_FAVORITE = "is_favorite"
        const val MEDIA_STORE_URI = "media_store_uri"
        const val MIME_TYPE = "mime_type"
        const val ORIENTATION = "orientation"
        const val SIZE_BYTES = "size_bytes"
        const val STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension"
        const val STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3 // 0x3
        const val STANDARD_MIME_TYPE_EXTENSION_GIF = 1 // 0x1
        const val STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2 // 0x2
        const val STANDARD_MIME_TYPE_EXTENSION_NONE = 0 // 0x0
        const val SYNC_GENERATION = "sync_generation"
        const val WIDTH = "width"
    }

    object AlbumColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DISPLAY_NAME = "display_name"
        const val ID = "id"
        const val MEDIA_COUNT = "album_media_count"
        const val MEDIA_COVER_ID = "album_media_cover_id"
    }

    object MediaCollectionInfo {
        const val ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent"
        const val ACCOUNT_NAME = "account_name"
        const val LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation"
        const val MEDIA_COLLECTION_ID = "media_collection_id"
    }
}

Ява

public final class CloudMediaProviderContract {

  public static final String EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID";
  public static final String EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED";
  public static final String EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID";
  public static final String EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE";
  public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
  public static final String EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL";
  public static final String EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED";
  public static final String EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION";
  public static final String MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS";
  public static final String PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER";
}

// Columns available for every media item
public static final class CloudMediaProviderContract.MediaColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DURATION_MILLIS = "duration_millis";
  public static final String HEIGHT = "height";
  public static final String ID = "id";
  public static final String IS_FAVORITE = "is_favorite";
  public static final String MEDIA_STORE_URI = "media_store_uri";
  public static final String MIME_TYPE = "mime_type";
  public static final String ORIENTATION = "orientation";
  public static final String SIZE_BYTES = "size_bytes";
  public static final String STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension";
  public static final int STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3; // 0x3
  public static final int STANDARD_MIME_TYPE_EXTENSION_GIF = 1; // 0x1 
  public static final int STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2; // 0x2 
  public static final int STANDARD_MIME_TYPE_EXTENSION_NONE = 0; // 0x0 
  public static final String SYNC_GENERATION = "sync_generation";
  public static final String WIDTH = "width";
}

// Columns available for every album item
public static final class CloudMediaProviderContract.AlbumColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DISPLAY_NAME = "display_name";
  public static final String ID = "id";
  public static final String MEDIA_COUNT = "album_media_count";
  public static final String MEDIA_COVER_ID = "album_media_cover_id";
}

// Media Collection metadata that is cached by the OS to compare sync states.
public static final class CloudMediaProviderContract.MediaCollectionInfo {

  public static final String ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent";
  public static final String ACCOUNT_NAME = "account_name";
  public static final String LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation";
  public static final String MEDIA_COLLECTION_ID = "media_collection_id";
}

onGetMediaCollectionInfo

Метод onGetMediaCollectionInfo() используется операционной системой для оценки достоверности кэшированных элементов облачного мультимедиа и определения необходимой синхронизации с поставщиком облачных мультимедиа. Из-за возможности частых вызовов операционной системы onGetMediaCollectionInfo() считается критически важным для производительности; Крайне важно избегать длительных операций или побочных эффектов, которые могут отрицательно повлиять на производительность. Операционная система кэширует предыдущие ответы этого метода и сравнивает их с последующими ответами, чтобы определить соответствующие действия.

Котлин

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

Ява

@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);

Возвращенный пакет MediaCollectionInfo включает следующие константы:

onQueryMedia

Метод onQueryMedia() используется для заполнения основной сетки фотографий в средстве выбора фотографий в различных представлениях. Эти вызовы могут быть чувствительны к задержке и могут вызываться как часть фоновой упреждающей синхронизации или во время сеансов выбора фотографий, когда требуется состояние полной или добавочной синхронизации. Пользовательский интерфейс средства выбора фотографий не будет ждать ответа бесконечно долго, чтобы отобразить результаты, и может блокировать время выполнения этих запросов в целях пользовательского интерфейса. Возвращенный курсор по-прежнему будет пытаться быть обработан в базе данных средства выбора фотографий для будущих сеансов.

Этот метод возвращает Cursor , представляющий все элементы мультимедиа в коллекции мультимедиа, дополнительно отфильтрованные по предоставленным дополнительным функциям и отсортированные в обратном хронологическом порядке MediaColumns#DATE_TAKEN_MILLIS (сначала самые последние элементы).

Возвращенный пакет CloudMediaProviderContract включает следующие константы:

Поставщик облачных носителей должен установить CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID как часть возвращаемого Bundle . Отсутствие этой установки является ошибкой и делает возвращаемый Cursor недействительным. Если поставщик облачных носителей обрабатывал какие-либо фильтры в предоставленных дополнительных функциях, он должен добавить ключ к ContentResolver#EXTRA_HONORED_ARGS как часть возвращаемого Cursor#setExtras .

onQueryDeletedMedia

Метод onQueryDeletedMedia() используется для обеспечения правильного удаления удаленных элементов в облачной учетной записи из пользовательского интерфейса средства выбора фотографий. Из-за потенциальной чувствительности к задержке эти вызовы могут быть инициированы как часть:

  • Фоновая проактивная синхронизация
  • Сеансы выбора фотографий (когда требуется полная или добавочная синхронизация)

Пользовательский интерфейс средства выбора фотографий отдает приоритет быстрому реагированию пользователя и не будет ждать ответа бесконечно. Для обеспечения бесперебойного взаимодействия могут возникать тайм-ауты. Любой возвращенный Cursor по-прежнему будет пытаться быть обработан в базе данных средства выбора фотографий для будущих сеансов.

Этот метод возвращает Cursor , представляющий все удаленные элементы мультимедиа во всей коллекции мультимедиа в текущей версии поставщика, возвращенные onGetMediaCollectionInfo() . Эти элементы могут быть дополнительно отфильтрованы по дополнительным функциям. Поставщик облачных носителей должен установить CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID как часть возвращаемого Cursor#setExtras Не установка этого параметра является ошибкой и делает Cursor недействительным. Если поставщик обрабатывал какие-либо фильтры в предоставленных дополнительных функциях, он должен добавить ключ в ContentResolver#EXTRA_HONORED_ARGS .

onQueryAlbums

Метод onQueryAlbums() используется для получения списка альбомов Cloud, доступных у поставщика облака, и связанных с ними метаданных. Дополнительные сведения см. CloudMediaProviderContract.AlbumColumns .

Этот метод возвращает Cursor , представляющий все элементы альбома в медиа-коллекции, дополнительно отфильтрованные по предоставленным дополнительным функциям и отсортированные в обратном хронологическом порядке AlbumColumns#DATE_TAKEN_MILLIS , сначала самые последние элементы. Поставщик облачных носителей должен установить CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID как часть возвращаемого Cursor . Отсутствие этой установки является ошибкой и делает возвращаемый Cursor недействительным. Если поставщик обрабатывал какие-либо фильтры в предоставленных дополнительных функциях, он должен добавить ключ к ContentResolver#EXTRA_HONORED_ARGS как часть возвращаемого Cursor .

onOpenMedia

Метод onOpenMedia() должен возвращать полноразмерный носитель, определенный предоставленным mediaId . Если этот метод блокируется при загрузке контента на устройство, вам следует периодически проверять предоставленный CancellationSignal , чтобы прервать непринятые запросы.

onOpenPreview

Метод onOpenPreview() должен возвращать миниатюру заданного size для элемента с предоставленным mediaId. Миниатюра должна иметь исходный CloudMediaProviderContract.MediaColumns#MIME_TYPE и, как ожидается, будет иметь гораздо меньшее разрешение, чем элемент, возвращаемый onOpenMedia . Если этот метод заблокирован при загрузке контента на устройство, вам следует периодически проверять предоставленный CancellationSignal , чтобы прервать непринятые запросы.

onCreateCloudMediaSurfaceController

Метод onCreateCloudMediaSurfaceController() должен возвращать CloudMediaSurfaceController используемый для предварительного просмотра элементов мультимедиа, или null , если предварительный просмотр не поддерживается.

CloudMediaSurfaceController управляет предварительным просмотром элементов мультимедиа в заданных экземплярах Surface . Методы этого класса предназначены для асинхронной работы и не должны блокироваться при выполнении каких-либо тяжелых операций. Один экземпляр CloudMediaSurfaceController отвечает за рендеринг нескольких элементов мультимедиа, связанных с несколькими поверхностями.

CloudMediaSurfaceController поддерживает следующий список обратных вызовов жизненного цикла: