酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Androidで 静止画と動画のピッカーで選択したコンテンツのファイルパスを取得する

Androidで 静止画と動画のピッカーを表示して、単一のコンテンツを選択する方法です。

静止画と動画のピッカーを表示する

val REQUEST_PICK_MEDIA = 10001

// メディアピッカーを表示する
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
startActivityForResult(intent, REQUEST_PICK_MEDIA)

実行すると下図のようにピッカーが表示されます。

選択したコンテンツのURIを取得する

選択したコンテンツのURIを取得することができます。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    val context = context ?: return

    if (requestCode == REQUEST_PICK_MEDIA) {
        val mediaUri = data?.data ?: return
        if (mediaUri.toString().contains("image")) {
            // 静止画の場合の処理
        } else if (mediaUri.toString().contains("video")) {
            // 動画の場合の処理
        }
    }
}

これで、あとは静止画と動画かを振り分けて処理することができま……せん。

この時得られるURIは content://com.android.providers.media.documents/document/video%3A26 のようになっています。URIからファイルパスを得ることはできますが、読み取り権限がないためコンテンツにアクセスすることができません。

「ファイルパスを取得する権限」を取得する

https://stackoverflow.com/questions/32661221/android-cursor-didnt-have-data-column-not-found/33930169#33930169 を参考にして、URIからファイルパスを取得する処理を実装しました。権限がないと下記のようにエラーが発生します。

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=68538, result=-1, data=Intent { dat=content://com.android.providers.media.documents/document/video:27 flg=0x43 }} to activity {APP_NAME}: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/file from pid=27739, uid=10201 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()

private var mCropImageUri: Uri? = null
private val REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 2000


override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    val context = context ?: return

    if (requestCode == REQUEST_PICK_MEDIA) {
        val mediaUri = data?.data ?: return
        if (mediaUri.toString().contains("image")) {
            //静止画の場合の処理
        } else if (mediaUri.toString().contains("video")) {
            if (context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                mCropImageUri = mediaUri
                requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_PERMISSION_READ_EXTERNAL_STORAGE)
            } else {
                // 動画の場合の処理
            }
        }
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    when (requestCode) {
        REQUEST_PERMISSION_READ_EXTERNAL_STORAGE -> {
            if (mCropImageUri != null && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 権限が取得できたので処理する!!!
            } else {
                // 権限が取れなかった場合
            }
        }
    }
}

URIからファイルパスを取得する

https://stackoverflow.com/questions/32661221/android-cursor-didnt-have-data-column-not-found/33930169#33930169 の処理をベースに一部だけ変更しています。これでURIからファイルパスを取得することができます。

@JvmStatic
fun getPathFromUri(context: Context, uri: Uri?): String? {
    val uri = uri ?: return null

    // DocumentProvider
    Log.e("uri", "uri:" + uri.authority)

    if (DocumentsContract.isDocumentUri(context, uri)) {
        if ("com.android.externalstorage.documents" ==
                uri.authority) { // ExternalStorageProvider
            val docId = DocumentsContract.getDocumentId(uri)
            val split = docId.split(":".toRegex()).toTypedArray()
            val type = split[0]
            return if ("primary".equals(type, ignoreCase = true)) {
                Environment.getExternalStorageDirectory().toString() + "/" + split[1]
            } else {
                "/stroage/" + type + "/" + split[1]
            }
        } else if ("com.android.providers.downloads.documents" ==
                uri.authority) { // DownloadsProvider
            val id = DocumentsContract.getDocumentId(uri)
            val contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
            return getDataColumn(context, contentUri, null, null)
        } else if ("com.android.providers.media.documents" ==
                uri.authority) { // MediaProvider
            val docId = DocumentsContract.getDocumentId(uri)
            val split = docId.split(":".toRegex()).toTypedArray()
            val type = split[0]
            var contentUri: Uri? = null
            contentUri = MediaStore.Files.getContentUri("external")
            val selection = "_id=?"
            val selectionArgs = arrayOf(
                    split[1]
            )
            return getDataColumn(context, contentUri, selection, selectionArgs)
        }
    } else if ("content".equals(uri.scheme, ignoreCase = true)) { //MediaStore
        return getDataColumn(context, uri, null, null)
    } else if ("file".equals(uri.scheme, ignoreCase = true)) { // File
        return uri.path
    }
    return null
}

fun getDataColumn(context: Context, uri: Uri?, selection: String?,
                selectionArgs: Array<String>?): String? {
    var cursor: Cursor? = null
    val projection = arrayOf(
            MediaStore.Files.FileColumns.DATA
    )
    try {
        cursor = context.contentResolver.query(
                uri, projection, selection, selectionArgs, null)
        if (cursor != null && cursor.moveToFirst()) {
            val cindex = cursor.getColumnIndexOrThrow(projection[0])
            return cursor.getString(cindex)
        }
    } finally {
        cursor?.close()
    }
    return null
}

以上で content://com.android.providers.media.documents/document/video%3A26 のようなURIから /storage/emulated/0/DCIM/Camera/20200601_104448.mp4 といったファイルパスを取得することができました。

参考記事

動画確認環境

  • Android Studio 4.0
  • デバイス: Galaxy A7 (Android 9)