Android Fetch All Files From Local Storage (Media Store API) 🥲

Muhammad Saeed
5 min readJul 19, 2022

--

Read file if you can

Hi android devs! Hope you are all fine but not fine with the media store api, cause it is good but not good for us. In this article we will fetch all files from local storage from android lollipop+, as well as in android 10 and android 11+.😎

1. Declare permissions on Manifest 🤯

First you need READ_EXTERNAL_STORAGE permission for reading files in non-scope storage android version, second permission is WRITE_EXTERNAL_STORAGE permission and third is MANAGE_EXTERNAL_STORAGE permission for android 11+.

And added android:requestLegacyExternalStorage=”true” for disabling the scope storage. 😒

<manifest>    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application

android:requestLegacyExternalStorage="true" />
</manifest>

Do you wonder why I added WRITE_EXTERNAL_STORAGE permission? because android 10 is too shy droid he doesn’t want to show the documents files (pdf, doc, ppt etc) but when you just add WRITE_EXTERNAL_STORAGE permission it is automatically become the non-shy droid.

(I was debug the issue for 3 days that my documents files are not readable in only android 10 and I find out that the issue is WRITE_EXTERNAL_STORAGE permission is not added and but I was thinking why android 10 need this permission? So I concluded android 10 is the shy droid)

Android 10 shy droid

2. Handle Permission 😅

Now we need to handle permission for android marshmallow to android 9, android 10 and android 11+

  • For android marshmallow to android 9 you need just READ_EXTERNAL_STORAGE permission.
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission Granted
} else {
// Permission not granted
}
}
private fun checkPermission(permission: String) =
ContextCompat.checkSelfPermission(
fragment.requireContext(),
permission
) == PackageManager.PERMISSION_GRANTED

fun requestReadExternalStoragePermission() {
when {
checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
// Permission Granted
}
shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
// Show rationale permission dialog and open settings
}
else -> {
requestPermissionLauncher.launch(
Manifest.permission.READ_EXTERNAL_STORAGE
)
}
}
}
  • For Android 10 you need 2 permisions READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE 🤯.
private val requestAndroid10PermissionLauncher =
fragment.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { list: Map<String, Boolean> ->
if (list.all { it.value }) {
permissionGranted.invoke(true)
} else {
permissionGranted.invoke(false)
}
}
fun requestReadExternalStoragePermission() {
when {
checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
// Permission granted
}
shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
// Show rationale dialog
}
else -> {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
requestAndroid10PermissionLauncher.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
} else {
requestPermissionLauncher.launch(
Manifest.permission.READ_EXTERNAL_STORAGE
)
}
}
}
}
private fun checkPermission(permission: String) =
ContextCompat.checkSelfPermission(
fragment.requireContext(),
permission
) == PackageManager.PERMISSION_GRANTED
  • For android 11+ you need READ_EXTERNAL_STORAGE and open settings as ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION to read all files in android 11+.
  • Environment.isExternalStorageManager() is use to get status of MANAGE_EXTERNAL_STORAGE permission in android 11+.
private val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// Permission Granted for android 11+
} else {
showAndroid11PlusPermissionDialog()
}
} else {
// Permission Granted for android marshmallow+
}
} else {
// Permission not granted
}
}

private val android11PlusSettingResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (Environment.isExternalStorageManager()) {
// Permission Granted for android 11+
} else {
// Permission not granted
}
}

fun requestReadExternalStoragePermission() {
when {
checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// Permission Granted for android 11+
} else {
showAndroid11PlusPermissionDialog()
}
} else {
// Permission Granted
}
}
shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// Permission Granted
return
}
}
// show rational dialog
}
else -> {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
requestAndroid10PermissionLauncher.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
} else {
requestPermissionLauncher.launch(
Manifest.permission.READ_EXTERNAL_STORAGE
)
}
}
}
}

private fun showAndroid11PlusPermissionDialog() {
MaterialAlertDialogBuilder(fragment.requireContext())
.setTitle(fragment.getString(R.string.allow_access))
.setMessage(fragment.getString(R.string.allow_access_detail))
.setPositiveButton(fragment.getString(R.string.open_setting)) { dialog, _ ->
val intent = Intent().apply {
action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
data
= Uri.fromParts("package", fragment.requireContext().packageName, null)
}
dialog.dismiss()
android11PlusSettingResultLauncher.launch(intent)
}
.setNegativeButton(fragment.getString(R.string.not_now)) { dialog, _ ->
dialog.dismiss()
}
.create()
.show()
}

private fun checkPermission(permission: String) =
ContextCompat.checkSelfPermission(
fragment.requireContext(),
permission
) == PackageManager.PERMISSION_GRANTED

3. MEDIA STORE QUERY 😎

Now the final step to query the files columns from the media store api as follows:

private fun getAllMediaFilesCursor(): Cursor? {
val projections =
arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.DATE_MODIFIED,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.SIZE
)

val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Files.getContentUri("external")
}

return context.contentResolver.query(
collection,
projections,
null,
null,
null
)
}

Above method return the cursor for all media.

suspend fun loadAllFilesToDatabase() {
val cursor = getAllMediaFilesCursor()

if (true == cursor?.moveToFirst()) {
val idCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val pathCol = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)
val nameCol = cursor.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
val dateCol = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATE_MODIFIED)
val mimeType = cursor.getColumnIndex(MediaStore.Files.FileColumns.MIME_TYPE)
val sizeCol = cursor.getColumnIndex(MediaStore.Files.FileColumns.SIZE)

do {
val id = cursor.getLong(idCol)
val path = cursor.getStringOrNull(pathCol) ?: continue
val name = cursor.getStringOrNull(nameCol) ?: continue
val dateTime = cursor.getLongOrNull(dateCol) ?: continue
val type = cursor.getStringOrNull(mimeType) ?: continue
val size = cursor.getLongOrNull(sizeCol) ?: continue
val contentUri = ContentUris.appendId(
MediaStore.Files.getContentUri("external").buildUpon(),
id
).build()

val media = "Uri:$contentUri,\nPath:$path,\nFileName:$name,\nFileSize:$size,\nDate:$dateTime,\ntype:$type"

Timber.d("Media: $media")

} while (cursor.moveToNext())
}
cursor?.close()
}

Finally, you loaded all your files from local storage. Please make sure you follow all the steps. In the end Android is easy 😒.

Bonus Panel

If you want to fetch only documents you need to update getAllMediaFilesCursor() but first you need to get your documents file mime types. So first I added mime type as enum class.

import android.webkit.MimeTypeMap // Import this please

enum class FileTypes(val mimeTypes: List<String?>) {
PDF(
listOf(
MimeTypeMap.getSingleton().getMimeTypeFromExtension("pdf"),
),
),
WORD(
listOf(
MimeTypeMap.getSingleton().getMimeTypeFromExtension("doc"),
MimeTypeMap.getSingleton().getMimeTypeFromExtension("docx"),
),
),
PPT(
listOf(
MimeTypeMap.getSingleton().getMimeTypeFromExtension("ppt"),
MimeTypeMap.getSingleton().getMimeTypeFromExtension("pptx"),
),
),
EXCEL(
listOf(
MimeTypeMap.getSingleton().getMimeTypeFromExtension("xls"),
MimeTypeMap.getSingleton().getMimeTypeFromExtension("xlsx"),
),
),
}

and now update the getAllMediaFilesCursor() function as

private fun getAllMediaFilesCursor(): Cursor? {

val projections =
arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATA, //TODO: Use URI instead of this.. see official docs for this field
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.DATE_MODIFIED,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.SIZE
)

val sortBy = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC"

val selectionArgs =
FileTypes.values().map { it.mimeTypes }.flatten().filterNotNull().toTypedArray()

val args = selectionArgs.joinToString {
"?"
}

val selection =
MediaStore.Files.FileColumns.MIME_TYPE + " IN (" + args + ")"

val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Files.getContentUri("external")
}

return context.contentResolver.query(
collection,
projections,
selection,
selectionArgs,
sortBy
)
}

Thank you for reading this article 😎 and don’t forget to clap this article 😁, Hope you learn something new or finally fetch all the file in android with ease but not ease 🥲!!! Here is the gist for fetch all docs from the media store api. Check this out.

https://gist.github.com/saeed-younus/d2c8de8b0e4dde1a77e3a973288c3813

Find out me on twitter and github ✌️

--

--