Créer un fournisseur de services multimédias cloud pour Android

Un fournisseur de services multimédias cloud fournit du contenu multimédia cloud supplémentaire sur Android sélecteur de photos. Les utilisateurs peuvent sélectionner les photos ou vidéos fournies par le fournisseur de services multimédias cloud lorsqu'une application utilise ACTION_PICK_IMAGES ou ACTION_GET_CONTENT pour demander des fichiers multimédias à l'utilisateur. Un contenu multimédia cloud fournisseur peut également fournir des informations sur les albums, que vous pouvez parcourir dans Sélecteur de photos Android.

Avant de commencer

Tenez compte des éléments suivants avant de commencer à créer votre infrastructure fournisseur de services multimédias.

Éligibilité

Android exécute un programme pilote pour permettre la migration vers le cloud des applications nommées par les OEM fournisseurs de services multimédias. Seules les applications désignées par les OEM peuvent participer à ce programme pour devenir, à l'heure actuelle, un fournisseur de services multimédias cloud pour Android. Chaque L'OEM peut désigner jusqu'à trois applications. Une fois approuvées, ces applications deviennent accessibles des fournisseurs de services multimédias cloud sur tous les appareils GMS Android sur lesquels ils installés.

Android tient à jour une liste côté serveur de tous les fournisseurs de services cloud éligibles. Chaque OEM Possibilité de choisir un fournisseur de services cloud par défaut à l'aide d'une superposition configurable Nomination les applications doivent répondre à toutes les exigences techniques et réussir tous les tests de qualité. Pour apprendre en savoir plus sur le processus du programme pilote OEM pour les fournisseurs de médias cloud exigences, remplissez le formulaire de demande.

Déterminer si vous devez créer un fournisseur de services multimédias cloud

Les fournisseurs de services multimédias cloud sont conçus pour être des applications ou des services principale pour sauvegarder et récupérer des photos et des vidéos dans le cloud. Si votre application possède une bibliothèque de contenus utiles, mais qu'elle n'est généralement pas utilisée solution de stockage de photos, envisagez de créer un fournisseur de documents à la place.

Un seul fournisseur cloud actif par profil

Il ne peut y avoir qu'un seul fournisseur de services multimédias cloud actif à la fois pour chaque Android profil. Les utilisateurs peuvent supprimer ou modifier le fournisseur multimédia cloud sélectionné l'application à tout moment dans les paramètres du sélecteur de photos.

Par défaut, le sélecteur de photos Android tente de choisir un fournisseur de services cloud. automatiquement.

  • S'il n'y a qu'un seul fournisseur de services cloud éligible sur l'appareil, cette application automatiquement sélectionné comme fournisseur actuel.
  • Si plusieurs fournisseurs de services cloud éligibles sont installés sur l'appareil et que l'un de correspondent à la valeur par défaut choisie par l'OEM, l'application choisie par l'OEM sera sélectionnée.

  • S'il y a plus d'un fournisseur de services cloud éligible sur l'appareil et qu'aucun de correspondent aux paramètres par défaut choisis par l'OEM, aucune application ne sera sélectionnée.

Créer votre fournisseur de services multimédias cloud

Le schéma suivant illustre la séquence des événements avant et pendant une session de sélection de photos entre l'application Android, le sélecteur de photos Android, le MediaProvider de l'appareil local et un CloudMediaProvider.

<ph type="x-smartling-placeholder">
</ph> Schéma séquentiel illustrant le flux du sélecteur de photos vers un fournisseur de services multimédias cloud
Figure 1 : Diagramme de séquence d'événements lors d'une session de sélection de photos
  1. Le système initialise le fournisseur de services cloud préféré de l'utilisateur synchronise les métadonnées multimédias avec le backend du sélecteur de photos Android.
  2. Lorsqu'une application Android lance le sélecteur de photos, avant d'afficher une image locale fusionnée ou une grille d'éléments cloud à l'utilisateur, le sélecteur de photos effectue une synchronisation incrémentielle avec le fournisseur de services cloud pour s'assurer que les résultats sont aussi à jour que possible. Après réception d'une réponse ou lorsque le délai est atteint, le La grille du sélecteur de photos affiche désormais toutes les photos accessibles en combinant celles stockées localement sur votre appareil avec celles synchronisées à partir du cloud.
  3. Pendant que l'utilisateur fait défiler la page, le sélecteur de photos extrait les vignettes multimédias d'un fournisseur de services multimédias cloud pour l'afficher dans l'UI.
  4. Lorsque l'utilisateur termine la session et que les résultats incluent un média cloud le sélecteur de photos demande des descripteurs de fichier pour le contenu, puis génère URI, et accorde à l'application appelante l'accès au fichier.
  5. L'application peut désormais ouvrir l'URI et dispose d'un accès en lecture seule aux contenus multimédias contenus. Par défaut, les métadonnées sensibles sont masquées. Sélecteur de photos utilise le système de fichiers FUSE pour coordonner l'échange de données entre l'application Android et le fournisseur de services multimédias cloud.

Problèmes courants

Voici quelques points importants à prendre en compte implémentation:

Éviter les fichiers en double

Comme le sélecteur de photos Android n'a aucun moyen d'inspecter l'état multimédia dans le cloud, CloudMediaProvider doit fournir MEDIA_STORE_URI dans le curseur ligne d'un fichier existant à la fois dans le cloud et sur l'appareil local, ou la l'utilisateur verra les fichiers en double dans le sélecteur de photos.

Optimiser les tailles d'image pour l'affichage des aperçus

Il est très important que le fichier renvoyé par onOpenPreview ne soit pas résolution d'image et respecte le Size demandé. Image trop grande entraîne des temps de chargement dans l'interface utilisateur, et une image trop petite risque d'être pixélisée ou floue en fonction de la taille de l'écran de l'appareil.

Gérer la bonne orientation

Si les vignettes renvoyées dans onOpenPreview ne contiennent pas leurs données EXIF, elles doivent être renvoyés dans le bon sens pour éviter la rotation des vignettes ; incorrectement dans la grille d'aperçu.

Empêcher les accès non autorisés

Vérifiez l'MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION avant de renvoyer les données à l'appelant à partir de ContentProvider. Vous empêcherez ainsi les applications non autorisées l'accès aux données cloud.

La classe CloudMediaProvider

Issu de android.content.ContentProvider, le CloudMediaProvider inclut des méthodes semblables à celles présentées dans l'exemple suivant:

Kotlin

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
}

Java

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

Classe CloudMediaProviderContract

En plus de la classe d'implémentation principale CloudMediaProvider, Le sélecteur de photos Android intègre une classe CloudMediaProviderContract. Ce cours décrit l'interopérabilité entre le sélecteur de photos et le cloud. fournisseur de services multimédias, englobant des aspects tels que MediaCollectionInfo pour opérations de synchronisation, colonnes Cursor prévues et extras Bundle.

Kotlin

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

Java

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

La méthode onGetMediaCollectionInfo() est utilisée par le système d'exploitation pour d'évaluer la validité de ses éléments multimédias cloud mis en cache et de déterminer la synchronisation avec le fournisseur de services multimédias cloud. En raison de la possibilité par le système d'exploitation, onGetMediaCollectionInfo() est considéré comme les performances essentielles ; il est essentiel d'éviter les opérations de longue durée qui pourraient avoir un impact négatif sur les performances. Le système d'exploitation met en cache réponses précédentes de cette méthode et les compare aux réponses suivantes pour déterminer les mesures à prendre.

Kotlin

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

Java

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

Le bundle MediaCollectionInfo renvoyé comprend les constantes suivantes:

onQueryMedia

La méthode onQueryMedia() permet de remplir la grille de photos principale dans sélecteur de photos dans différentes vues. Ces appels peuvent être sensibles à la latence. peut être appelé lors d'une synchronisation proactive en arrière-plan ou lors du sélecteur de photos lorsqu'un état de synchronisation complet ou incrémentiel est requis. Sélecteur de photos l'interface utilisateur n'attend pas indéfiniment une réponse pour afficher les résultats et pourrait expirer ces demandes pour des raisons d’interface utilisateur. Curseur renvoyé tentera tout de même d'être traité dans la base de données du sélecteur de photos sessions.

Cette méthode renvoie un Cursor représentant tous les éléments multimédias de ce type de contenu. collection facultative filtrée par les extras fournis et triée dans l'ordre inverse ordre chronologique de MediaColumns#DATE_TAKEN_MILLIS (éléments les plus récents) d'abord).

Le bundle CloudMediaProviderContract renvoyé comprend les éléments suivants : constantes:

Le fournisseur de services multimédias cloud doit définir CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID dans l'élément renvoyé Bundle Si vous ne définissez pas ce paramètre, cela entraînera une erreur et invalidera le Cursor renvoyé. Si que le fournisseur de services multimédias cloud a géré tous les filtres dans les extras fournis, il doit ajouter la clé de ContentResolver#EXTRA_HONORED_ARGS dans l'objet renvoyé Cursor#setExtras

onQueryDeleteMedia

La méthode onQueryDeletedMedia() permet de s'assurer que les éléments supprimés dans le compte cloud sont correctement supprimés de l'interface utilisateur du sélecteur de photos. Motif : leur sensibilité potentielle à la latence, ces appels peuvent être initiés pour:

  • Synchronisation proactive en arrière-plan
  • Sessions avec sélecteur de photos (quand une synchronisation complète ou incrémentielle est requise)

L'interface utilisateur du sélecteur de photos donne la priorité à une expérience utilisateur réactive et n'attendra pas indéfiniment une réponse. Pour maintenir des interactions fluides, les délais avant expiration peuvent se produire. Tout Cursor renvoyé tentera quand même d'être traité dans la base de données du sélecteur de photos pour les sessions futures.

Cette méthode renvoie un Cursor représentant tous les éléments multimédias supprimés dans la collection complète de médias dans la version actuelle du fournisseur, telle que renvoyée par onGetMediaCollectionInfo() Vous pouvez éventuellement filtrer ces éléments par "extras". Le fournisseur de services multimédias cloud doit définir CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID dans l'élément renvoyé Cursor#setExtras Si vous ne définissez pas ce paramètre, il s'agit d'une erreur et invalide le Cursor. Si le fournisseur a géré tous les filtres dans les extras fournis, il doit ajouter la clé à le ContentResolver#EXTRA_HONORED_ARGS.

onQueryAlbums

La méthode onQueryAlbums() permet d'extraire une liste des albums Cloud qui disponibles auprès du fournisseur de services cloud, ainsi que les métadonnées associées. Voir CloudMediaProviderContract.AlbumColumns pour en savoir plus.

Cette méthode renvoie un Cursor représentant tous les éléments de l'album dans le contenu multimédia. collection facultative filtrée par les extras fournis et triée dans l'ordre inverse ordre chronologique de AlbumColumns#DATE_TAKEN_MILLIS , éléments les plus récents en premier. Le fournisseur de services multimédias cloud doit définir CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID dans l'élément renvoyé Cursor Si vous ne définissez pas ce paramètre, cela entraînera une erreur et invalidera le Cursor renvoyé. Si le fournisseur a géré tous les filtres dans les extras fournis, il doit ajouter la clé à ContentResolver#EXTRA_HONORED_ARGS dans l'élément Cursor renvoyé.

OnOpenMedia

La méthode onOpenMedia() doit renvoyer le support en taille réelle identifié par le mediaId fourni. Si cette méthode se bloque lors du téléchargement de contenu l'appareil, vérifiez régulièrement le CancellationSignal fourni pour annuler les demandes abandonnées.

onOpenPreview

La méthode onOpenPreview() doit renvoyer une vignette de la size pour l'élément de l'élément mediaId fourni. La miniature doit se trouver CloudMediaProviderContract.MediaColumns#MIME_TYPE d'origine et devrait avoir une résolution beaucoup plus faible que celle de l'élément renvoyé par onOpenMedia. Si cette méthode est bloqué pendant le téléchargement de contenu sur l'appareil, veillez à vérifier régulièrement Vérifiez le CancellationSignal fourni pour annuler les requêtes abandonnées.

onCreateCloudMediaSurfaceController

La méthode onCreateCloudMediaSurfaceController() doit renvoyer une CloudMediaSurfaceController utilisé pour afficher l'aperçu des éléments multimédias ; ou null si l'aperçu n'est pas disponible.

CloudMediaSurfaceController gère l'affichage de l'aperçu des éléments multimédias sur des instances données de Surface. Les méthodes de cette classe sont censées être asynchrone et ne doit pas être bloqué en effectuant une opération lourde. Une seule L'instance CloudMediaSurfaceController est chargée d'afficher plusieurs éléments multimédias associés à plusieurs surfaces.

Le CloudMediaSurfaceController est compatible avec la liste suivante de Rappels de cycle de vie: