I am creating an app in which I list the recent images and videos from the user's gallery and display them on a RecyclerView using the file path. The code below is working on Android 7, but Android Studio is warning that MediaStore.Files.FileColumns.DATA has been deprecated. What to use instead?
// Get relevant columns for use later.
val projection = arrayOf(
MediaStore.Files.FileColumns.DATA
)
// Return only video and image metadata.
val selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
val queryUri: Uri = MediaStore.Files.getContentUri("external")
val cursorLoader = CursorLoader(
requireContext(),
queryUri,
projection,
selection,
null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC" // Sort order.
)
val cursor: Cursor? = cursorLoader.loadInBackground()
if (cursor!!.moveToFirst()) {
do {
val columnIndex = cursor.getColumnIndex(projection[0])
val filePath = cursor.getString(columnIndex)
val mimeType = URLConnection.guessContentTypeFromName(filePath)
if (mimeType != null && mimeType.startsWith("image")) {
mediaList.add(GalleryMedia(filePath, "image"))
} else if (mimeType != null && mimeType.startsWith("video")) {
mediaList.add(GalleryMedia(filePath, "video"))
}
} while (cursor.moveToNext())
}
cursor.close()
Related
I'm trying to query for every file in an android device using the mediastore API. Is there a shorter way of writing the selection string to include all the files in the device without having to write each for the Audio,Video,Images and NON_MEDIA? Also,how can I make the query,in addition,to get only the files of above a particular file size.
ContentResolver cr = context.getContentResolver();
Uri uri = MediaStore.Files.getContentUri("external");
// every column, although that is huge waste, you probably need
// BaseColumns.DATA (the path) only.
String[] projection = null;
// exclude media files, they would be here also.
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE + "OR" + MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO;
String[] selectionArgs = null; // there is no ? in selection so null here
String sortOrder = null; // unordered
Cursor allFiles = cr.query(uri, projection, selection, selectionArgs, sortOrder);
Is there a shorter way of writing the selection string to include all
the files in the device without having to write each for the
Audio,Video,Images and NON_MEDIA?
yes, MediaStore.Files
This is how I achieved (below code is in kotlin)
private suspend fun getAllFiles(context : Context) : List<AllFiles>{
return withContext(Dispatchers.IO){
val fileCollection = mutableListOf<AllFiles>()
val contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
val projections = arrayOf(MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.MIME_TYPE)
context.contentResolver.query(contentUri,projections,null,null,null).use {
cursor->
if(cursor!=null && cursor.count > 0) {
val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
val typeIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE)
fileCollection.clear()
while (cursor.moveToNext()){
val id = cursor.getLong(idIndex)
val displayName = cursor.getString(displayNameIndex)
val type = cursor.getString(typeIndex)
fileCollection.add(AllFiles(id,displayName,type))
}
}
else if(cursor==null)
Log.d("myTag","cursor is nUll")
else
Log.d("myTag","Cursor count is ${cursor.count}")
}
fileCollection
}
}
To run above method on android 12 , we need
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
outside of onCreate
val intent = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:" + BuildConfig.APPLICATION_ID))
val storagePermissionResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()
) {
if (Environment.isExternalStorageManager()) {
// Permission granted.
getData()
}
}
and inside onCreate
if(!Environment.isExternalStorageManager())
storagePermissionResultLauncher.launch(intent)
else
getData()
where getData() is
fun getData(){
val textview = binding.textviewFirst
lifecycleScope.launchWhenResumed {
textview.append(getAllFiles(requireContext()).toString())
}
}
and AllFile is
data class AllFiles(
val fileId : Long,
val displayName : String?,
val type : String?)
you can then filter returned list on type
I am trying to implement a recyclerview which will show all the music files in my android device with a 10 per page pagination and contains a search text. I am using content resolver for this. My application is targeting till android 12. So my existing code is like:
fun getAllMusicFilesInDevice(offset: Int, searchText: String){
val tempAudioList: MutableList<Album> = ArrayList()
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
else
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Audio.AudioColumns.DATA,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.ArtistColumns.ARTIST,
)
val selection = MediaStore.Audio.Media.DISPLAY_NAME + " LIKE ?"
val selectionArgs = arrayOf(searchText)
val sortOrder =
"${MediaStore.Audio.Media.DISPLAY_NAME} ASC LIMIT $PAGE_SIZE OFFSET $offset"
val c = mContext.contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder
)
if (c != null) {
while (c.moveToNext()) {
try {
val path: String = c.getString(0)
val songName = c.getString(1)
val album: String = c.getString(2)
val artist: String = c.getString(3)
val extension = File(path).extension
if (isMusicFile(extension)) {
isMusicPresentInPlaylist(path, playlistDb) {
val audioModel =
Album(
name = songName,
path = path,
artist = artist,
album = album
)
tempAudioList.add(audioModel)
}
}
} catch (ex: Exception) {
}
}
c.close()
}}
This code does not give back any result and makes the app behave like its stuck. Please help me out on the mistakes I am making and anything I am doing wrong out here so that I get this function working.
UPDATE CURSOR CODE
val c = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val limit = Bundle().apply {
// Limit & Offset
putInt(ContentResolver.QUERY_ARG_LIMIT, AUDIO_PAGE_SIZE)
putInt(ContentResolver.QUERY_ARG_OFFSET, offset)
}
mContext.contentResolver.query(uri, projection, limit, null)
} else
mContext.contentResolver.query(
uri,
projection,
null,
null,
"${MediaStore.Audio.Media.DISPLAY_NAME} ASC LIMIT $AUDIO_PAGE_SIZE OFFSET $offset"
)
Note: this code is running in a background thread.
I am using media-store for listing pdf in recycler view.
Media store does not load non-media files on android 10 and above but works for media files
In build.gradle
compileSdkVersion 30
buildToolsVersion "30.0.2"
minSdkVersion 21
targetSdkVersion 30
FetchPdf.kt
class FetchPdf(private val applicationContext: Context) {
private val arrayList = ArrayList<Pdf>()
fun getPdf(): ArrayList<Pdf>{
val collection = MediaStore.Files.getContentUri("external")
val projection = arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATE_MODIFIED,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATA
)
val mimeType = "application/pdf"
val whereClause = MediaStore.Files.FileColumns.MIME_TYPE + " IN ('" + mimeType + "')"
val orderBy = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC"
val cursor: Cursor? = applicationContext.contentResolver.query(
collection,
projection,
whereClause,
null,
orderBy
)
if (cursor != null) {
val idCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val nameCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)
val dataCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)
if (cursor.moveToFirst()) {
do {
val fileUri: Uri = Uri.withAppendedPath(
MediaStore.Files.getContentUri("external"),
cursor.getString(idCol)
)
val name = cursor.getString(nameCol)
val data = cursor.getString(dataCol)
val size = cursor.getLong(sizeCol)
arrayList.add(Pdf(name, fileUri.toString(), data))
} while (cursor.moveToNext())
}
cursor.close()
}
return arrayList
}
}
In Manifest:
I have only READ_EXTERNAL_STORAGE permission
What I have tried:
I tried changing MediaStore.Files.FileColumns.DATA to MediaStore.Files.FileColumns.RELATIVE_PATH not working
I tried traversing each and every folder recursively still not working
Any help would be appreciated.
I created a function to fetch videos
private val projectionVideo = arrayOf(
MediaStore.Video.Media._ID, //ID
MediaStore.Video.Media.DISPLAY_NAME, //NAME
MediaStore.Video.Media.DATA, //PATH TO CONTENT
MediaStore.Video.Media.DATE_TAKEN, //DATE
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE
)
private fun queryVideos(cr: ContentResolver): Cursor? =
cr.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projectionVideo, null, null,
MediaStore.Video.Media.DATE_TAKEN + " $SORT_ORDER"
)
In Java
public static Uri getContentUri(String volumeName) {
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/video/media");
}
/**
* The content:// style URI for the internal storage.
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
/**
* The content:// style URI for the "primary" external storage
* volume.
*/
public static final Uri EXTERNAL_CONTENT_URI =
getContentUri("external");
The issue is this one not showing up any videos in any paths, like 'DCIM', 'WhatsApp Videos' etc. Every video I create with my own app will be correctly inserted, unlike the ones created by the default camera app, unless I open them first.
You can read all videos from Android Shared Storage with this code
private val videoProjection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DATA,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.MIME_TYPE,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATE_ADDED)
context.contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
videoProjection,
null,
null,
"${MediaStore.Video.Media.DATE_ADDED} DESC"
)?.use { cursor ->
while (cursor.moveToNext()) {
//read video attr from cursor
cursor.getLong(cursor.getColumnIndexOrThrow(videoProjection[0])) // read video id
cursor.getString(cursor.getColumnIndexOrThrow(videoProjection[1])) // read video path
// and .....
}
}
fun getAllVideos(): ArrayList<FileGallery>? {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
var columnVideo:Int ?= null
var columnFolder:Int ?= null
var columnDateAdded:Int ?= null
var absolutePathVideo:String ?= null
var folderName:String ?= null
var dateAdded:String ?= null
val uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Video.Media.DATA,
MediaStore.Video.VideoColumns.DATE_ADDED,
MediaStore.Video.Media.BUCKET_DISPLAY_NAME
)
val orderBy:String = MediaStore.Video.Media.DATE_TAKEN
val cursor = context.contentResolver.query(
uri,
projection,
null,
null,
"$orderBy DESC"
)
var listVideo = arrayListOf<FileGallery>()
columnVideo = cursor?.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
columnFolder = cursor?.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME)
columnDateAdded = cursor?.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.DATE_ADDED)
while (cursor?.moveToNext() == true){
absolutePathVideo = cursor.getString(columnVideo!!)
folderName = cursor.getString(columnFolder!!)
dateAdded = cursor.getString(columnDateAdded!!)
listVideo.add(FileGallery(absolutePathVideo, folderName, dateAdded))
}
return listVideo
}else{
return null
}
}catch (e:Exception){
Log.e("ERROR", e.message?:"")
return null
}
}
try this
I fetch contacts from the phone as below. I would like to ignore accent for exemple the name "Jérome" would be return either for search "jero" or "jéro".
var contacts = listOf<Contact>()
val displayNameProjection = arrayOf(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
val whereName = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?"
val whereNameParams = arrayOf( "%" + search + "%")
val contactCursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
displayNameProjection, whereName, whereNameParams,ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
contactCursor?.let { cursor ->
contacts = generateSequence { if (cursor.moveToNext()) cursor else null }
.take(10)
.map {
Contact( it.getString(it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)),
it.getString(it.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)))
}
.toList()
cursor.close()
}
Use the CONTENT_FILTER_URI API to quickly search contacts by name, this should handle accents automatically for you:
String name = "jero";
Uri searchUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(name));
Cursor cur = getContentResolver().query(searchUri, null, null, null, null);
DatabaseUtils.dumpCursor(cur);
if (cur != null) cur.close();