I am using DownloadManager to download a video in the Downloads directory. Once the video is downloaded, I query the download directory using MediaStore. The problem is that I am getting the video that was downloaded last to second instead of the latest one.
Video Download Code
val request = DownloadManager.Request(Uri.parse(downloadUrl))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"MyApp/CategoryOne/123.mp4"
)
.setTitle(fileName)
.setDescription(context.getString(R.string.all_text_downloading))
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadID = downloadManager.enqueue(request)
var isDownloadFinished = false
var downloadProgress: Int
while (!isDownloadFinished) {
val cursor: Cursor =
downloadManager.query(DownloadManager.Query().setFilterById(downloadID))
if (cursor.moveToFirst()) {
when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
DownloadManager.STATUS_FAILED -> {
isDownloadFinished = true
emit(State.error(context.getString(R.string.all_error_download_failed)))
}
DownloadManager.STATUS_PAUSED -> {
}
DownloadManager.STATUS_PENDING -> {
}
DownloadManager.STATUS_RUNNING -> {
val totalSizeInBytes: Long =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
if (totalSizeInBytes >= 0) {
val downloadedBytes: Long =
cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
downloadProgress =
(downloadedBytes * 100L / totalSizeInBytes).toInt()
emit(State.downloading(downloadProgress))
}
}
DownloadManager.STATUS_SUCCESSFUL -> {
downloadProgress = 100
emit(State.downloading(downloadProgress))
isDownloadFinished = true
val downloadedVideo = getDownloadedVideo()
emit(State.success(true))
}
}
}
}
MediaStore Code
fun getDownloadedVideo(): MediaStoreVideo {
lateinit var mediaStoreVideo: MediaStoreVideo
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATE_ADDED
)
val selection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) "${MediaStore.Video.Media.RELATIVE_PATH} like ? " else "${MediaStore.Video.Media.DATA} like ? "
val selectionArgs =
arrayOf("%MyApp/CategoryOne%")
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"
context.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val dateAddedColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_ADDED)
val displayNameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
if (cursor.moveToFirst()) {
val id = cursor.getLong(idColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
id
)
val displayName = cursor.getString(displayNameColumn)
val size = cursor.getLong(sizeColumn)
val dateAdded =
Date(TimeUnit.SECONDS.toMillis(cursor.getLong(dateAddedColumn)))
mediaStoreVideo = MediaStoreVideo(id, contentUri, displayName, size, dateAdded)
}
}
return mediaStoreVideo
}
This is the approach that I have followed to get the info of the video that is last downloaded by my app using DownloadManager. Also, I am not sure if this is the best approach to do the same. Any help will be appreciated. Thanks
After a bit of testing, I think it takes a few milliseconds before the entry is added to the MediaStore. I added a delay of 1000 ms and everything works now as expected.
However, this is a temporary solution and if someone can suggest a better approach, it would be more helpful.
Related
I use this function to show images from specific album in gallery:
fun findImagesInAlbum(albumId: String): List<Image> {
val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projections = arrayOf(
MediaStore.Images.ImageColumns._ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DISPLAY_NAME
)
val selection = "${MediaStore.Images.ImageColumns.BUCKET_ID} == ?"
val selectionArgs = arrayOf(albumId)
val findImages = HashMap<String, Image>()
contentResolver.query(contentUri, projections, selection, selectionArgs,
"${MediaStore.Images.ImageColumns.DATE_TAKEN} ASC")?.use { cursor ->
if (cursor.moveToFirst()) {
val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID)
val displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME)
do {
val mediaId = cursor.getLong(idIndex)
val filename = cursor.getString(displayNameIndex)
val uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
mediaId
)
val image = Image(
id = mediaId.toString(),
name = filename,
uri = uri,
count = 0,
)
findImages[mediaId.toString()] = image
image.count++
} while (cursor.moveToNext())
}
}
return findImages.values.toList().sortedByDescending { it.name }
}
I show the List<Image> at the RecyclerView and it works fine. After clicking on image it opens on fullscreen. What functions can i use to rename an opened image and save its name in the phone storage?
I tried to solve this problem with DocumentsContract.renameDocument, but it throws error: java.lang.UnsupportedOperationException: Unsupported call: android:renameDocument.
This code is working fine when images read from gallery but when my phone reset and I recovered images from Google photos. It recovered to my gallery, but when I fetch images from gallery programmatically, it doesn't work properly. it just fetched 2 months images. I have used this code, Using Date_added meta data, but I have already tested date_taken and date_modified but it doesn't work.
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
/* MediaStore.Images.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL
)*/
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val projection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.SIZE,
MediaStore.MediaColumns.DATA,
MediaStore.Images.Media.IS_FAVORITE
)
} else {
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.SIZE,
MediaStore.MediaColumns.DATA
)
}
// Show only images that are at least 5 minutes in duration.
// val selection = MediaStore.Images.Media.DATE_TAKEN + ">=? and " + MediaStore.Images.Media.DATE_TAKEN + "<=?"
// val selectionArgs = arrayOf(
// "" + startDateTimeStamp, "" + endDateTimestamp
// )
// Display images in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} ASC"
val query = contentResolver.query(
collection,
projection,
null,
null,
sortOrder
)
query?.use {
cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val dateModifiedColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val relativePath = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
val isFav = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.IS_FAVORITE)
} else {
0
}
/*
val relativePathColumn =
cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH)
*/
while (cursor.moveToNext()) {
// Get values of columns for a given Image.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val date = cursor.getString(dateModifiedColumn)
val size = cursor.getInt(sizeColumn)
val relativePath = cursor.getString(relativePath);//cursor.getInt(isFav)
val isFav = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
cursor.getInt(isFav)
}else{
0
}
/*
val album1 = cursor.getInt(album)
val albumArtist = cursor.getInt(albumArtist)
*/
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
val dateTakenCalendar = date?.toLongOrNull()?.let {
val calendar = Calendar.getInstance()
calendar
}?: kotlin.run {
Calendar.getInstance()
}
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.
The following code is used to save files from the app to downloads:
Uri collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, filename);
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
ContentResolver contentResolver = context.getApplicationContext().getContentResolver();
Uri uri = contentResolver.insert(collection, values);
OutputStream outputStream = context.getApplicationContext().getContentResolver().openOutputStream(uri, "w");
Everything is saved, however, if you delete the file from downloads manually, and then try to download it again from the application, an error appears:
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE[2067])
Is there a way to fix this or will I have to use unique names for each download?
This error happens because the machanism of OS: if we delete manually a file (media), its database will not be deleted immediately. Until we restart device.
Have a approach for this problem (still not be optimized - hope receiving sharing from people), such as:
Step 1: Get info of file via its name
Step 2: Ask OS to update its database via MediaScannerConnection.scanFile
Step 3: Use current code that has above problem
Codes for steps (collected on internet)
Step 1:
fun findByFileName(fileName: String): MutableList<FileInfo> {
val files = mutableListOf<FileInfo>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATA
)
val selection = "${MediaStore.Video.Media.DISPLAY_NAME} LIKE ?"
val selectionArgs = arrayOf(
fileName
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"
val query = context.contentResolver.query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
while (cursor.moveToNext()) {
// Get values of columns for a given video.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val duration = cursor.getInt(durationColumn)
val size = cursor.getInt(sizeColumn)
val data = cursor.getStringOrNull(dataColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
files += FileInfo(contentUri, name, duration, size, data)
}
}
return files
}
data class FileInfo(
val uri: Uri,
val name: String,
val duration: Int,
val size: Int,
val data: String? = null
)
Step 2 + Step 3:
val exitedData = findByFileName(fileName = name)
if (exitedData != null) {
MediaScannerConnection.scanFile(context, arrayOf(exitedData.first().data.toString()), null, object: MediaScannerConnection.OnScanCompletedListener {
override fun onScanCompleted(path: String?, uri: Uri?) {
// Step 3
// Use current code have this problem
...
}
}
} else {
// Save file normally
// Use current code have this problem
...
}
I added the request premissions for reading files tried this method inside my activity but nothing shows,like there is no images in real devices and emulator,what am i doing wrong?
fun storaheread() {
val imageProjection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media._ID
)
val imageSortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC"
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageProjection,
null,
null,
imageSortOrder
)
cursor.use {
it?.let {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val dateColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val name = it.getString(nameColumn)
val size = it.getString(sizeColumn)
val date = it.getString(dateColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// add the URI to the list
// generate the thumbnail
// val thumbnail = (this as Context).contentResolver.loadThumbnail(contentUri, Size(480, 480), null)
Log.d("image name",name)
}
} ?: kotlin.run {
Log.e("TAG", "Cursor is null!")
}
}
}
you can check out this similar question, it may help you achieve this situation.
Loading all the images from gallery into the Application in android