SAFでファイルピッカーの初期フォルダを指定

投稿日:  更新日:

Storage Access Framework(SAF)は、ファイルピッカーでアクセス対処のファイルを指定する仕組みです。

ファイルピッカーの初期フォルダは、前回の状態が引き継がれます。

また、初期フォルダを意図的に指定することも可能です。

今回は「初期フォルダの指定方法」について、まとめます。

※環境:Android Studio Narwhal Feature Drop | 2025.1.2 Patch 2

スポンサーリンク

初期フォルダの指定方法

初期フォルダの指定方法は、リクエスト(Intent)でEXTRA_INITIAL_URIにフォルダのURIを設定します。

※ストレージへSAFでアクセスする方法については以下を参照
外部ストレージへStorage Access Frameworkでアクセス
SAFのACTION_OPEN_DOCUMENT_TREEでフォルダを指定

OPEN_DOC_TREECREATE_DOCOPEN_DOC
fun TreeOpenIntent(initUri: Uri) =
    Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
        putExtra(EXTRA_INITIAL_URI, initUri)
    }
fun FileCreateIntent(filename: String, initUri: Uri) =
    Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        putExtra(Intent.EXTRA_TITLE, filename)
        putExtra(EXTRA_INITIAL_URI, initUri)
        type = "*/*"
    }
fun FileOpenIntent(initUri: Uri) =
    Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        putExtra(EXTRA_INITIAL_URI, initUri)
        type = "*/*"
    }
スポンサーリンク

初期フォルダの指定例

以下は、初期フォルダの指定例です。※エミュレータ(API 33)で確認

例:既存のフォルダ

初期フォルダを外部ストレージ_プライマリのDCIMフォルダにします。

                            //   ↓↓ content://com.android.externalstorage.documents/document/primary%3ADCIM
                            val _initUri = DocumentsContract.buildDocumentUri(
                                "com.android.externalstorage.documents",	// authority
                                "primary:${Environment.DIRECTORY_DCIM}"		// documentId
                            )
                            _safReadTreePicker.launch(TreeOpenIntent(_initUri))
                            //
_safReadTreePicker
fun ComponentActivity.SafPicker(callback: (Uri) -> Unit) =
    registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if(result.resultCode == RESULT_OK) {  // ファイル指定が成功
            val _intent = result.data
            _intent?.let {
                val _uri = it.data
                _uri?.let { callback(it) }    // 2段目コールバックの実行
            }
        }
        else { }                              // ファイル指定が失敗
    }
        val _safReadTreePicker = SafPicker { uri ->
            val _docFile = DocumentFile.fromTreeUri(this@Activity, uri)
            /**
             * フォルダに対する処理
             */
        }
# pwd
/storage/emulated/0
# ls -ld * DCIM/*
drwxrws--- 2 u0_a173  media_rw  4096 2025-08-29 04:54 Alarms
drwxrws--x 5 media_rw media_rw  4096 2025-08-29 04:54 Android
drwxrws--- 2 u0_a173  media_rw  4096 2025-08-29 04:54 Audiobooks
drwxrws--- 2 u0_a173  media_rw  4096 2025-08-29 05:16 DCIM
-rw-rw---- 1 u0_a173  media_rw 61009 2025-08-29 05:16 DCIM/bike.jpg
-rw-rw---- 1 u0_a173  media_rw 54206 2025-08-29 05:16 DCIM/donguri.jpg
-rw-rw---- 1 u0_a173  media_rw 84773 2025-08-29 05:16 DCIM/leaf1.jpg
-rw-rw---- 1 u0_a173  media_rw 82076 2025-08-29 05:16 DCIM/leaf2.jpg
...

初期フォルダ:DCIM

例:トップフォルダ

初期フォルダを外部ストレージ_プライマリのトップフォルダにします。

                            //   ↓↓ content://com.android.externalstorage.documents/document/primary%3A
                            val _initUri = DocumentsContract.buildDocumentUri(
                                "com.android.externalstorage.documents",	// authority
                                "primary:"									// documentId
                            )
                            _safReadTreePicker.launch(TreeOpenIntent(_initUri))
                            //

初期フォルダ:トップ

スポンサーリンク

注意:Documentsプロバイダー向けURIを使う

初期フォルダとして指定するURIは、Documentsプロバイダー向けURIを使います。

Documentsプロバイダー向けURIは、次のようなものです。

content://com.android.externalstorage.documents/document/primary%3ADCIM
content://com.android.externalstorage.documents/document/primary%3A

※赤字の部分は以下のようになる。初期フォルダの指定に影響しない。
 ACTION_CREATE_DOCUMENT    -> "document"
 ACTION_OPEN_DOCUMENT      -> "document"
 ACTION_OPEN_DOCUMENT_TREE -> "tree"

その他のURI(例:Mediaプロバイダー向け、など)は受け付けません。

URIを生成するユーティリティとして、DocumentsContract.buildDocumentUriが用意されています。※使用方法は上記の「初期フォルダの指定例」を参照

スポンサーリンク

問題点:Authorityは埋め込み

Androidシステム内に複数のプロバイダーが存在しています。SAFが通信を行うDocumentsプロバイダーは、その中の一つです。

稼働中のプロバイダのAuthority
                            val _list = this@Activity.getPackageManager().queryIntentContentProviders(Intent(PROVIDER_INTERFACE), 0)
                            _list.forEach {		// ResolveInfo
                                val _auth = it.providerInfo.authority
                                Log.i(TAG, "AUTH = ${_auth}")
                            }
AUTH = com.android.externalstorage.documents
AUTH = com.android.mtp.documents
AUTH = com.android.providers.downloads.documents
AUTH = com.android.providers.media.documents

対象のプロバイダーはUriのAuthority部で識別されます。

DocumentsプロバイダーのAuthorityをプログラム的に参照したいところですが、API内のAuthorityパラメータが一般向けに公開されていません。

...
    /**
     * External Storage Provider's authority string
     * {@hide}
     */
    @SystemApi
    public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
            "com.android.externalstorage.documents";
...

ですので、プロブラム中に値を埋め込むしかありません。

                            val _initUri = DocumentsContract.buildDocumentUri(
                                "com.android.externalstorage.documents",	// authority(埋め込み)
                                "primary:${Environment.DIRECTORY_DCIM}"		// documentId
                            )
スポンサーリンク

問題点:外部ストレージ_セカンダリの扱い

外部ストレージ_セカンダリのフォルダのURIは、次のようになります。

content://com.android.externalstorage.documents/document/1EEF-081A%3ADCIM
content://com.android.externalstorage.documents/document/1EEF-081A%3A

※赤字の部分は以下のようになる。初期フォルダの指定に影響しない。
 ACTION_CREATE_DOCUMENT    -> "document"
 ACTION_OPEN_DOCUMENT      -> "document"
 ACTION_OPEN_DOCUMENT_TREE -> "tree"

エミュレータでは、URIのQuery部が「1EEF-081A」です。「1EEF-081A」は外部ストレージ_セカンダリのuuidです。

StorageVolumeのuuid
※エミュレータ(API 33)で確認

                                val _sm = this@Activity.getSystemService(STORAGE_SERVICE) as StorageManager
                                _sm.getStorageVolumes().forEach {
                                    it.dump(this@Activity)
                                }
fun StorageVolume.dump(context: Context) {
    Log.i(TAG, "Volume = ${this}")
    Log.i(TAG, "SV description = ${getDescription(context)}")
    Log.i(TAG, "SV isEmulated = ${isEmulated}")
    Log.i(TAG, "SV isPrimary = ${isPrimary}")
    Log.i(TAG, "SV isRemovable = ${isRemovable}")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)  // API≧30
        Log.i(TAG, "SV mediaStoreVolumeName = ${mediaStoreVolumeName}")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)  // API≧33
        Log.i(TAG, "SV owner = ${owner}")
    Log.i(TAG, "SV state = ${state}")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)  // API≧31
        Log.i(TAG, "SV storageUuid = ${storageUuid}")
    Log.i(TAG, "SV uuid = ${uuid}")
}
Volume = StorageVolume: SDCARD (1EEF-081A)
SV description = SDCARD
SV isEmulated = false
SV isPrimary = false
SV isRemovable = true
SV mediaStoreVolumeName = 1eef-081a
SV owner = UserHandle{0}
SV state = mounted
SV storageUuid = null
SV uuid = 1EEF-081A

ですので、次のように生成したURIで、初期フォルダが指定できます。

                            val _initUri = DocumentsContract.buildDocumentUri(
                                "com.android.externalstorage.documents",	// authority(埋め込み)
                                "1EEF-081A:${Environment.DIRECTORY_DCIM}"	// documentId
                            )

初期フォルダ:DCIM(外部ストレージ_セカンダリ)

しかし、この方法は問題点があります。

  • ・「セカンダリがuuidで表現される」という説明がドキュメントにない
  • ・uuidの計算アルゴリズムがエミュレータと同じである保証がない

つまり、端末のメーカによって、扱いが違うかも知れません。

問題点を考えると、「初期フォルダの指定を行わない」とするのが、良さそうです。

スポンサーリンク

関連記事:

ストレージのリソースは、内部メモリー、外部メモリー、クラウドの3つがあります。 外部メモリーは、主にSDカードです。 このSDカードはAndroid端末の世代が進むにつれて、扱いを変えてきました。 リソースとSDカードを主眼に置いたストレージの変遷について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
Adoptable Storage(API≧23、Android 6.0)が導入されて、SDカードの扱い方をユーザ側で指定できるようになりました。 「Adoptable Storage」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
ストレージはデータの用途別に記憶領域が分けられます。 保存先を守らないと、セキュリティリスクが発生したり、他のアプリと協調した動作が出来なくなったり、します。 ですので、適切な場所へデータを保存しましょう。 今回は「用途別記憶領域とボリューム」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
ストレージへアクセスする方法は、「扱うデータの種類」「アクセス先」「セキュリティの確保」などの要件により、最適なアクセス方法が存在するので、使い分けが必要です。 今回は「アクセスする方法」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
外部ストレージへアクセスするアプリは、パーミッション(許可)をユーザーから取得する必要があります。 パーミッションを得たアプリは、許可された権限の範囲内で、ストレージへアクセスが可能です。 これらは、ユーザーデータのプライバシー保護と密接に関係しています。 プライバシー保護をより強固にするために、パーミッションと権限の範囲の仕様は改変されてきました。 今回は、改変の歴史を辿りつつ、「パーミッションと権限の範囲」について、まとめます。 ※環境:Android Studio Narwhal | 2025.1.1 Patch 1 ...
Storage Access Framework(SAF)は、ファイルピッカーでアクセス対処のファイルを指定する仕組みです。 SAFはアンドロイドシステムに実装されています。SAFを用いれば、ピッカーを自作する必要はありません。 アプリ開発が楽になります。 しかも、指定された外部ストレージ上のファイルは、アプリに対するアクセス許可を付与したものになります。 改めて、許可の取得は不要です。そのまま、ファイルを読み書き出来ます。 今回は「外部ストレージへStorage Access Frameworkでアクセス」を、まとめます。 ※環境:Android Studio Narwhal Feature Drop | 2025.1.2 Patch 1 ...
Storage Access Framework(SAF)は、ファイルピッカーでアクセス対処のファイルを指定する仕組みです。 API≧21で、ACTION_OPEN_DOCUMENT_TREEアクションが追加され、フォルダを指定するリクエストが構築できるようになりました。 今回は「SAFのACTION_OPEN_DOCUMENT_TREEでフォルダを指定」について、まとめます。 ※環境:Android Studio Narwhal Feature Drop | 2025.1.2 Patch 1     androidx.documentfile:documentfile:1.1.0 ...
スポンサーリンク