USB ストレージ メディア

MediaStore

アプリによる Android のメディア コンテンツの閲覧とアクセスには、MediaStore を使用することをおすすめします。MediaStore は、使用可能なすべてのボリュームをインデックスに登録し、オーディオ、動画、画像コンテンツをアプリで使用できるようにします。MediaStore API の使用例が LocalMediaPlayer です。

Adoptable Storage

ドライブは、頻繁に切断される可能性の低い安定した場所に接続されていれば、取り込み可能と判断できます。USB ポートを fstab で安定していると示すには、encryptable=userdata 属性を使用します(デバイス設定をご覧ください)。

Adoptable Port に接続されているドライブはすべてインデックスに登録されます。処理時間がかかるだけでなく、結果として得られるインデックスは内部フラッシュ ストレージに書き込まれます。

Android 9 の既知の制限事項

Android 9 では、ドライブが抜かれると Adoptable Drive のインデックスが削除されていました。つまり、ユーザーがドライブを取り外してすぐに再接続すると、元のインデックスがフラッシュ ストレージから削除され、ドライブのインデックスが再作成されると書き換えられます。

また、Android 9 では、誤って起動時にドライブが切断されていると判断され、同じように削除とインデックスの再作成が行われるという競合状態も発生していました。最悪の場合、車の電源を入れるたびにローカル フラッシュ ストレージへの書き換えが発生する可能性があります。

Android 10 の新機能

Android 10 では、MediaStore の多くの点が改善されています。たとえば、上記の競合状態が修正されています。 ドライブが取り外されると、そのインデックスは、ドライブが最後に検出された日付から 1 週間保持されます。この期間中にドライブが再接続された場合、インデックスのフラッシュ ストレージへの書き換えは発生しません。これは、フレームワークによって複数のドライブに対して行われます。

ドライブが再接続された場合、コンテンツが変更されると、インデックスは完全に書き換えられるのではなく更新されます。これにより、フラッシュ ストレージへの影響を最小限に抑えられます。このチェックは起動時にも行われます。ユーザーが車両の電源を切った後にドライブを取り外し、新しいメディアを追加した後、車両を起動する前にドライブを再接続した場合、ドライブのインデックスが更新され、新しく追加されたメディアが追加されます。

その他の要件

ポートを Adoptable として設定すると、このポートに接続されているドライブをシステム ストレージの一部として組み込むことができます。そうなると、ドライブはデバイスのストレージの拡張として扱われるため、まずポータブル メディアに戻してから、ドライブを取り外す必要があります。

ドライブが接続されると、フレームワークから通知が行われ、ユーザーがドライブを組み込むことができます。これでウィザード フローが開始され、ユーザーはドライブをフォーマットできるようになります。 Android 10 では、このフローは車の設定には組み込まれていません。 ポートを Adoptable としてマークする際にこの機能がサポートされていることを確認するのは、OEM の責任です。CtsAppSecurityHostTestCases は Adoptable Port に関連する機能をカバーしており、実行する必要があります。

メディアの変更を検出する

実行中のプロセス(アクティビティまたはサービス)は MediaStore.AUTHORITY_URIContentObserver を登録できます。これにより、すべての組み込み可能なボリュームにおけるコンテンツの変更をオブザーバーに通知できます。

ContentObserver mContentObserver = new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        if (isResumed()) {
            // take action here
        }
    }
};

context.getContentResolver().registerContentObserver(
MediaStore.AUTHORITY_URI, true, mContentObserver);

notifyForDescendantstrue に設定すると、オブザーバーは、その URI について明示的に通知されるわけではなく、その URI で使用可能なメディアに変更があった場合に通知されます。その結果、新しく接続されたドライブがインデックスに登録されている間に、新しく追加された URI ごとにこのオブザーバーがトリガーされます。

Android 10 では、JobScheduler を使用して、実行中のプロセスがなくてもコンテンツの変更を監視できます。ただし、デバイスで使用可能なリソースがある場合にのみジョブが実行されます。

ボリュームの一覧表示

Android 10 では、新しい MediaStore API を使用して、インデックスに登録されている、またはインデックスに登録中のすべてのボリューム名を取得できます。

List<String> volumeNames = MediaStore.getExternalVolumeNames(context);

ボリューム コンテンツの閲覧

プロセスは、Adoptable Volume の名前を使用して、このボリューム内の特定タイプのメディア(音声、動画、画像、またはファイル)の URI を取得し、その下にあるインデックスに登録された利用可能なファイルをすべてクエリします。

import android.provider.MediaStore.Audio.Media;

Uri volumeAudioUri = Media.getContentUri(volumeName);
String[] projection = {Media._ID, Media.ARTIST, Media.TITLE};

Cursor cursor = getContext().getContentResolver().query(volumeAudioUri, projection, null, null);

全ボリュームの閲覧

MediaStore の各コンテンツ タイプには、EXTERNAL_CONTENT_URIINTERNAL_CONTENT_URI があります。これらを使用すると、外部ボリュームと内部ボリュームにおけるすべてのコンテンツ リストを取得できます。この 2 つを組み合わせて利用可能なすべてのメディアを表示する方法の例については、packages/apps/Car/LocalMediaPlayer をご覧ください。

必要な権限

外部ストレージからの読み取りを行おうとするアプリは、READ_EXTERNAL_STORAGE 権限をリクエストする必要があります。

再生中のメディア

特定のカーソル アイテムに対して URI を生成できます。

Long mediaId = cursor.getLong(cursor.getColumnIndex(Media._ID));
Uri mediaUri = ContentUris.withAppendedId(volumeAudioUri, mediaId);

そこで、アプリは音声フォーカスをリクエストし、コンテンツを再生できます。メディアアプリ作成に関するおすすめの方法については、音声と動画の概要をご覧ください。

MediaStore の代替方法

MediaStore と ContentResolver を使用して、Adoptable Drive 上のコンテンツにアクセスすることをおすすめします。ただし、非 Adoptable USB ポートの場合、別の方法で変更を検出し、コンテンツにアクセスできます。

接続されたボリュームの変更を検出する

システムアプリは、接続されたボリュームの変更をリッスンできます。StorageEventListener は、ボリュームがチェックされているときや取り外されているとき、またはボリュームがマウントまたはマウント解除されたときなど、ボリュームの状態が変更されたときに通知できます。

StorageEventListener mStorageEventListener = new StorageEventListener() {
    @Override
    public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
        if (isResumed()) {
            // take action here
        }
    }
};

StorageManager mStorageManager = context.getSystemService(StorageManager.class);
mStorageManager.registerListener(mStorageEventListener);

この方法は、実行中のプロセス内でのみ使用できます。

ストレージ アクセス フレームワークによるアクセス

Adoptable Drive のメディア以外のファイル、または非 Adoptable Drive の任意のファイルにアクセスするには、アプリでストレージ アクセス フレームワーク(SAF)を使用する必要があります。ACTIONS_OPEN_DOCUMENT インテントをトリガーすると、すべてのボリュームから 1 つ以上のファイルを選択するためのシステム UI がユーザーに表示されます。このインテントの呼び出し元アプリでは、MIME タイプを指定して、ユーザーが選択できるファイルを、アプリがアクションを実行しようとしているファイルにフィルタする必要があります。

注: アプリで SAF を使ってファイルにアクセスするには、ユーザーにプロンプトを表示して明示的にファイルを選択させる必要があります。Android は、汎用ファイル UI アクセス APK を提供します。この APK は、拡張してスタイルを設定することも、OEM 提供の APK に置き換えることもできます。