Skip to content

Instantly share code, notes, and snippets.

@saeed-younus
Created July 19, 2022 17:35
Show Gist options
  • Save saeed-younus/d2c8de8b0e4dde1a77e3a973288c3813 to your computer and use it in GitHub Desktop.
Save saeed-younus/d2c8de8b0e4dde1a77e3a973288c3813 to your computer and use it in GitHub Desktop.
Fetch All docs files from local storage using media store api (Android 10+ also covered)
manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="">
<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" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".PdfReaderApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Splash"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
import android.webkit.MimeTypeMap
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"),
),
),
}
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.os.Build
import android.provider.MediaStore
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import javax.inject.Inject
class LoadFilesRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val appDatabase: AppDatabase,
) {
suspend fun loadAllFilesToDatabase() {
val mediaItems: MutableList<String> = mutableListOf()
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 file = appDatabase.fileDao().loadById(id)
val fileEntity = FileEntity(
id = id,
path = path,
uri = contentUri.toString(),
name = name,
dateTime = dateTime,
mimeType = type,
size = size,
bookmarked = if (file.isEmpty()) false else file.first().bookmarked,
)
appDatabase.fileDao().insert(fileEntity)
val media =
"Uri:$contentUri,\nPath:$path,\nFileName:$name,\nFileSize:$size,\nDate:$dateTime,\ntype:$type"
Timber.d("Media: $media")
mediaItems.add(media)
} while (cursor.moveToNext())
}
cursor?.close()
}
/**
* Returns a cursor pointing to each image/video file in the system
*/
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
)
}
}
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.invento.pdfreader.R
class ReadExternalStoragePermission(
private val fragment: Fragment,
private val permissionGranted: (Boolean) -> Unit,
) {
private val requestPermissionLauncher =
fragment.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
permissionGranted.invoke(true)
} else {
permissionGranted.invoke(false)
showAndroid10PlusPermissionDialog()
}
} else {
permissionGranted.invoke(true)
}
} else {
permissionGranted.invoke(false)
}
}
private val settingsResultLauncher =
fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
permissionGranted.invoke(true)
} else {
permissionGranted.invoke(false)
}
}
private val android11PlusSettingResultLauncher =
fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (Environment.isExternalStorageManager()) {
permissionGranted.invoke(true)
} else {
permissionGranted.invoke(false)
}
}
fun requestReadExternalStoragePermission() {
when {
checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
permissionGranted.invoke(true)
} else {
permissionGranted.invoke(false)
showAndroid10PlusPermissionDialog()
}
} else {
permissionGranted.invoke(true)
}
}
fragment.shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
permissionGranted.invoke(true)
return
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
showAndroid10PlusPermissionDialog()
} else {
showRationalPermissionDialog()
}
permissionGranted.invoke(false)
}
else -> {
requestPermissionLauncher.launch(
Manifest.permission.READ_EXTERNAL_STORAGE
)
}
}
}
private fun showRationalPermissionDialog() {
MaterialAlertDialogBuilder(fragment.requireContext())
.setTitle(fragment.getString(R.string.external_storage_permission))
.setMessage(fragment.getString(R.string.external_storage_rationale_message))
.setPositiveButton(fragment.getString(R.string.open_setting)) { dialog, _ ->
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", fragment.requireContext().packageName, null)
}
dialog.dismiss()
settingsResultLauncher.launch(intent)
}
.setNegativeButton(fragment.getString(R.string.close)) { dialog, _ ->
dialog.dismiss()
}
.create()
.show()
}
private fun showAndroid10PlusPermissionDialog() {
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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment