Android 用のクラウド メディア プロバイダを作成する

クラウド メディア プロバイダは、追加のクラウド メディア コンテンツを Android 写真選択ツールを使用します。ユーザーは、Google フォトから提供された写真や動画を選択して、 アプリが ACTION_PICK_IMAGES または Cloud Storage を使用している場合、クラウド メディア プロバイダ ACTION_GET_CONTENT: ユーザーにメディア ファイルをリクエストします。クラウド メディア プロバイダはアルバムに関する情報も提供できます。この情報は Android の写真選択ツール。

始める前に

クラウドの構築を開始する前に、次の点を考慮してください。 できます。

利用資格

Android は、OEM が推薦するアプリをクラウド化するためのパイロット プログラムを実施しています 接続できますOEM が推薦するアプリのみが参加可能 現時点で、このプログラムを Android のクラウド メディア プロバイダにする必要があります。各 OEM はアプリを 3 つまで推薦できます。承認されると、これらのアプリは GMS Android 搭載デバイス上のクラウド メディア プロバイダを接続します。 インストールされています。

Android は、対象となるすべてのクラウド プロバイダのリストをサーバーサイドで維持しています。各 OEM 構成可能なオーバーレイを使用してデフォルトのクラウド プロバイダを選択できます。ノミネート アプリはすべての技術要件を満たし、すべての品質テストに合格する必要があります。学習内容 OEM クラウド メディア プロバイダのパイロット プログラムのプロセスと お問い合わせフォームにご記入ください。

クラウド メディア プロバイダを作成する必要があるかどうかを判断する

クラウド メディア プロバイダとは、ユーザーのインターネット クラウドから写真や動画をバックアップして取得するためのメインソースです。 有用なコンテンツのライブラリがあるが、一般的に ドキュメント プロバイダの作成を検討することをおすすめします。 してください。

プロファイルごとに 1 つのアクティブなクラウド プロバイダ

Android 1 つにつき、一度に有効にできるクラウド メディア プロバイダは 1 社だけです。 プロファイルをご覧ください。ユーザーは、選択したクラウド メディア プロバイダを削除または変更できます いつでも写真選択ツールの設定で選択できます。

デフォルトでは、Android の写真選択ツールはクラウド プロバイダの選択を試みます。 自動的に適用されます。

  • デバイス上に有効なクラウド プロバイダが 1 つしかない場合、そのアプリは 現在のプロバイダとして自動的に選択されます
  • 対象のクラウド プロバイダがデバイス上に複数存在し、 OEM が選択したデフォルトと一致すると、OEM が選択したアプリが選択されます。

  • デバイス上に適格なクラウド プロバイダが複数あり、いずれのプロバイダも OEM が選択したデフォルトと一致すると、アプリは選択されません。

クラウド メディア プロバイダを構築する

次の図は、イベントの発生前と発生時のイベントの順序を示しています。 Android アプリ、Android の写真選択ツール、 ローカル デバイスの MediaProviderCloudMediaProvider です。

<ph type="x-smartling-placeholder">
</ph> 写真選択ツールからクラウド メディア プロバイダへのフローを示すシーケンス図
図 1: 写真選択セッション中のイベント シーケンスの図。
  1. システムはユーザーの優先クラウド プロバイダを初期化し、定期的に メディア メタデータを Android の写真選択ツールのバックエンドに同期します。
  2. Android アプリが写真選択ツールを起動してから、マージされたローカルを表示する場合 アイテム グリッドやクラウド アイテムのグリッドを表示すると、写真選択ツールがレイテンシの影響を受けやすいステップを実行します。 クラウド プロバイダと段階的に同期し、常に最新の結果が得られるようにする 必要があります。応答を受信した後、または期限に達したときに、 写真選択ツールのグリッドに、アクセス可能なすべての写真を表示し、保存されている写真を統合 クラウドから同期されたデータと同期できます
  3. ユーザーがスクロールすると、写真選択ツールが画像からメディアのサムネイルを UI に表示するクラウド メディア プロバイダです。
  4. ユーザーがセッションを完了し、結果にクラウド メディアが含まれている場合 アイテムを入力すると、写真選択ツールがコンテンツのファイル記述子をリクエストし、 URI を呼び出し、呼び出し元のアプリケーションにファイルへのアクセスを許可します。
  5. これで、アプリが URI を開けるようになり、メディアに対する読み取り専用アクセス権が付与されます。 できます。デフォルトでは、機密メタデータは秘匿化されます。写真選択ツール FUSE ファイル システムを利用して、VM インスタンス間の Android アプリとクラウド メディア プロバイダ。

一般的な問題

ここでは、Google Cloud のリソースについて、 実装:

ファイルの重複を避ける

Android の写真選択ツールにはクラウド メディアの状態を調べる方法がないため、 CloudMediaProvider はカーソルに MEDIA_STORE_URI を指定する必要があります。 クラウドとローカル デバイスの両方に存在するファイルの行、または 写真選択ツールに重複したファイルが表示されます。

プレビュー表示用の画像サイズを最適化する

onOpenPreview から返されるファイルが完全なファイルではないことが非常に重要です。 指定された Size に準拠しています。画像が大きすぎます UI での読み込み時間が発生したり、小さすぎると画像が粗くなったり、 ぼやけているように見えるかもしれません。

正しい向きを処理する

onOpenPreview で返されたサムネイルに EXIF データが含まれていない場合、 サムネイルが回転しないように、正しい向きで返す必要があります。 間違っています

不正アクセスの防止

データを返す前に MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION を確認します。 ContentProvider からの呼び出し元。これにより、不正なアプリが アクセスする方法を紹介します。

CloudMediaProvider クラス

android.content.ContentProvider から派生した CloudMediaProvider クラスには、次の例に示すようなメソッドが含まれます。

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

CloudMediaProviderContract クラス

プライマリ CloudMediaProvider 実装クラスに加えて、 Android の写真選択ツールに CloudMediaProviderContract クラスが組み込まれています。 このクラスでは、写真選択ツールとクラウドの相互運用性について概説します。 メディア プロバイダ(MediaCollectionInfo などを含む) 同期オペレーション、想定される Cursor 列、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

onGetMediaCollectionInfo() メソッドは、オペレーティング システムが次の目的で使用します。 キャッシュに保存されたクラウド メディア アイテムの有効性を評価して、 クラウドメディアプロバイダとの同期が 可能になります頻繁に発生する可能性があるため、 呼び出す場合は、onGetMediaCollectionInfo() が考慮されます。 パフォーマンス重視特に、長時間実行オペレーションや、長時間実行オペレーションを パフォーマンスに悪影響を及ぼす可能性があるオペレーティング システムは 後続のレスポンスと比較し、 適切なアクションを判断します

Kotlin

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

Java

@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() メソッドは、クラウド アルバムのリストを取得するために使用します。 クラウド プロバイダで入手可能であり、関連するメタデータが含まれています。詳しくは、 CloudMediaProviderContract.AlbumColumns

このメソッドは、メディア内のすべてのアルバム アイテムを表す Cursor を返します。 指定されたエクストラでフィルタされ、逆順に並べ替えられたコレクション(省略可) AlbumColumns#DATE_TAKEN_MILLIS 年が新しい順 見てみましょう。クラウド メディア プロバイダが 返される値の一部としての CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID Cursor。設定しないとエラーとなり、返された Cursor が無効になります。条件 プロバイダが指定されたエクストラでフィルタを処理した場合、プロバイダはキーを 返された Cursor の一部としての ContentResolver#EXTRA_HONORED_ARGS

onOpenMedia

onOpenMedia() メソッドは、 指定された mediaId。コンテンツのダウンロード中に、このメソッドがブロックすると、 指定された CancellationSignal を定期的に確認して中止する必要があります。 破棄されます。

OpenPreview

onOpenPreview() メソッドは、提供された動画のサムネイルを返す必要があります。 指定された mediaId のアイテムの size。サムネイルは 当初のCloudMediaProviderContract.MediaColumns#MIME_TYPEであり、予定されています onOpenMedia で返されるアイテムよりもはるかに低い解像度にする。この方法で デバイスにコンテンツをダウンロード中にブロックされる場合は、定期的に 指定された CancellationSignal を確認して、放棄されたリクエストを中止します。

onCreateCloudMediaSurfaceController

onCreateCloudMediaSurfaceController() メソッドは メディア アイテムのプレビューのレンダリングに使用する CloudMediaSurfaceController プレビュー レンダリングがサポートされていない場合は null

CloudMediaSurfaceController がメディア アイテムのプレビューのレンダリングを管理します。 Surface の特定のインスタンスに対して実行されました。このクラスのメソッドは、 負荷の高いオペレーションを実行してブロックしないようにします。単一の CloudMediaSurfaceController インスタンスが複数のレンダリングを担います。 複数のサーフェスに関連付けられたメディア アイテム。

CloudMediaSurfaceController は、次のリストをサポートしています。 ライフサイクル コールバックを呼び出せます。