Created
July 19, 2022 17:35
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | |
), | |
), | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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