I'm building an App that manages audiobook libraries
Using the Intent ACTION_OPEN_DOCUMENT_TREE to let the user choose a Directory as a library, I get an Uri of the form : content:// as a result.
Is there a way to convert the given "content://" Uri to a "file:// filepath" ? ( if that is possible of course )
Or can I tweak the file chooser activity to accept only folders that have an actual file:// path ?
Thank you very much
EDIT : progress !
I managed, using the content resolver, to find a path of the form "1407-1105:Audiobooks" for the SD card, and "primary:Audiobooks" for the main volume. That seems more readable, but I have the same problem still.
Finally found the solution ! Maybe it is a little bit ugly, but that does seems to work !
fun resolveContentUri(uri:Uri): String {
val docUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri))
val docCursor = context.contentResolver.query(docUri, null, null, null, null)
var str:String = ""
// get a string of the form : primary:Audiobooks or 1407-1105:Audiobooks
while(docCursor!!.moveToNext()) {
str = docCursor.getString(0)
if(str.matches(Regex(".*:.*"))) break //Maybe useless
}
docCursor.close()
val split = str.split(":")
val base: File =
if(split[0] == "primary") getExternalStorageDirectory()
else File("/storage/${split[0]}")
if(!base.isDirectory) throw Exception("'$uri' cannot be resolved in a valid path")
return File(base,split[1]).canonicalPath
}
Related
I am trying to get all audio contained in a folder that the user picks.
The user is asked to pick a folder like this
getUri = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
activityResultCallback
)
getUri.launch(null)
I am trying to query the folder like this
var selection = MediaStore.Audio.Media.DISPLAY_NAME + " != ?"
val selectionArgs = arrayListOf("")
if (data != null){
selection += " AND "
selection += if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
MediaStore.MediaColumns.RELATIVE_PATH + " LIKE ? "
} else {
MediaStore.Audio.Media.DATA + " LIKE ? "
}
selectionArgs.add("%$data%")
}
I have tried the following for data
uri.path.toString()
uri.encodedPath.toString()
uri.pathSegments.last().toString()
uri.pathSegments.joinToString ("/")
None of that is working. The query is empty when there should be three files. These files are in the MediaStore, as a query of all audio files in the MediaStore contains them. The docs are not very helpful (as usual). The stack overflow posts I found leave out how to get a path.
Perhaps I should ask how to get the user to pick a folder and get all the music from the folder they chose? The docs lead me to both code snippets above, so I would be floored if there was no way to connect them (except this is Android I am dealing with, so nothing surprises me anymore).
OpenDocumentTree gives you access to only the Uri you receive and its children. It does not give you access to any path at all, much less one you could use with the MediaStore APIs (nor would you want it to, given that the MediaStore APIs are terrible to begin with).
Instead, the easiest way to deal with the Uri returned by OpenDocumentTree is to pass it to DocumentFile.fromTreeUri(), which gives a DocumentFile object that you can call listFiles() on and use getType() to search for mime types starting with audio/:
// Assuming you have your uri returned by OpenDocumentTree
val rootDirectoryFile = DocumentFile.fromTreeUri(uri)
val directories = ArrayDeque(rootDirectoryFile)
val audioFileUris = mutableListOf<Uri>()
// Loop through all of the subdirectories, starting with the root
while (directories.isNotEmpty()) {
val currentDirectory = directories.removeFirst()
// List all of the files in the current directory
val files = currentDirectory.listFiles()
for (file in files) {
if (file.isDirectory) {
// Add subdirectories to the list to search through
directories.add(file)
} else if (file.type?.startsWith("audio/")) {
// Add Uri of the audio file to the list
audioFileUris += file.uri
}
}
}
// Now audioFileUris has the Uris of all audio files
I'm building a built-in file manager for my app and I'm using MediaStore to query for the files.
I got everything working, except that the URI I get back is not to Glide's liking.
class java.io.FileNotFoundException: /external/images/media/37: open failed: ENOENT (No such file or directory)
I understand the issue at hand is that there's no protocol added so glide doesn't know how to read it, but I don't know how to add the protocol, I was under the impression that using ContentUris.withAppendedId would handle that for me but it appears to not be.
val mediaList = mutableListOf<Uri>()
val query = buildQuery(folderId, onlyImages)
query?.use { cursor ->
val columnIndexId = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val columnIndexMimeType = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
val columnIndexDuration = cursor.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.DURATION)
while (cursor.moveToNext())
{
val id = cursor.getLong(columnIndexId)
val contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
mediaList.add(contentUri)
}
}
return mediaList
I'm trying to void using DATA as I already know that's depreciated and I just need this solution work with Android 8+.
Are you converting the contentUri to a path? You shouldn't be doing that. Your contentUri should look like it does below, and then Glide will accept it:
content://media/external/images/media/37
I am saving my pdf files in Documents Folder or Download Folder. I want to access the file via the primary storage as when I choose the file in the "Downloads" or "Documents", the uri is changed and the file name gets changed to something like msf:63 or document:63
here is the image of when choosing the file in which directory
so my problem is I want to get rid of the "Documents" and the "Downloads" so the user will not choose the file from there. Is that possible?
Here is the code when I try to open the file
Intent(Intent.ACTION_OPEN_DOCUMENT).also {
val mime = arrayOf("application/pdf", //pdf
"application/vnd.openxmlformats-officedocument.wordprocessingml.document") //docx
it.type = "application/*"
it.addCategory(Intent.CATEGORY_OPENABLE)
it.putExtra(Intent.EXTRA_MIME_TYPES, mime)
activityLauncher.launch(it){ its ->
if (its.resultCode == Activity.RESULT_OK){
val str = its.data!!.data!!.path!!.split("/").toTypedArray()
whichNoFileSelected(tag)!!.text = str[str.size - 1]
whichNoFileSelected(tag)!!.textSize = 11f
if(str[str.size - 1].contains(".docx")){
whichNoFileSelected(tag)!!.setCompoundDrawablesWithIntrinsicBounds(
requireContext().resources.getDrawable(R.drawable.ic_doc, null),
null,
null,
null
)
}else{
whichNoFileSelected(tag)!!.setCompoundDrawablesWithIntrinsicBounds(
requireContext().resources.getDrawable(R.drawable.ic_pdf, null),
null,
null,
null
)
}
uploadImage(its.data!!, "document")
}
}
}
I want to get rid of the "Documents" and the "Downloads" so the user will not choose the file from there. Is that possible?
No.
Also, please get rid of this line and the code that depends on it:
val str = its.data!!.data!!.path!!.split("/").toTypedArray()
Use getType() on a ContentResolver to find the MIME type associated with the content that the user chose. Not only will this handle the Documents and Downloads trees, but it will handle all the other Uri scenarios as well (e.g., user chooses a document from Google Drive).
New to Kotlin and i'm trying to simple get any video from gallery and open into device's default app player.
My function to get all videos. It seens to work well, but the returned Uri is like 'content://...', i don't know if this is the right or it should be something like 'file://...'
private val videos = mutableListOf<Uri>()
private fun getAllVideos() {
val uriExternal = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(MediaStore.Video.Media._ID)
contentResolver.query(uriExternal, projection, null, null, null)?.use { cursor ->
while (cursor.moveToNext()) {
val videoUri = Uri.withAppendedPath(uriExternal, "" + cursor.getString(0))
videos.add(videoUri)
}
}
}
Then i try to open the Uri like this, but i always get an error from the player and nothing works.
val intent = Intent(Intent.ACTION_VIEW).apply {
data = videos.first()
type = "video/*"
}
startActivity(intent)
I searched but dont found any updated tutorial that don't use "MediaStore.Video.Media.DATA" (it's deprecated now). Something i'm doing wrong?
but the returned Uri is like 'content://...', i don't know if this is the right
Yes, it is.
Then i try to open the Uri like this, but i always get an error from the player and nothing works.
First, either remove the type or use the correct MIME type. Do not use a wildcard.
Second, add FLAG_GRANT_READ_URI_PERMISSION to the Intent. Without it, the other app has no rights to access the content.
Also, make sure you only go through that code if there is at least one element in the list, as otherwise your first() call will throw an exception.
What I am trying to achieve is sounds very familiar, it has been posted many times here and there in Stack Overflow as well, but I'm unable to get it done.
The scenario is, I receive a mail with attachment having custom extension in it. The extension is recognized by my app and it needs the FilePath to process it.
Currently, when I get the attachment in my app using getIntent().getData() all I get is path of the form content://
I have seen methods to convert media content of the type content:// to FilePath like /sdcard/file.ext but I was unable to convert the attachment using that. May be its obvious.
Is there any way that I can process the content:// type without actually downloading it.
Currently from the k9 mail app, when I get the custom extension, it shows my app in the list and opens it through it, but I need FilePath like /sdcard/file.ext and I'm only able to get content:// type.
I hope I made the question clear.
Please Help.
Regards.
A content:// Uri does not necessarily point to a file on the sdcard.
It is more likely that it points to any kind of data stored in a database
or to a content provider that gives you access to the private file storage of another app.
I think the later one is the case with mail attachments (if the content provider is not requesting it directly from a web server). So converting the content:// Uri to a path will not work.
I did the following (not sure if it works also for k9 mail app)
Uri uri = intent.getData();
if (uri.getScheme().equals("content")) {
String fileName = ContentProviderUtils.getAttachmentName(this, uri);
if (fileName.toLowerCase().endsWith(".ext")) {
InputStream is = this.getContentResolver().openInputStream(uri);
// do something
} else {
// not correct extension
return;
}
} else if (uri.getScheme().equals("file")) {
String path = uri.getPath();
if (path.toLowerCase().endsWith(".ext")) {
InputStream is = new FileInputStream(path);
// do something
} else {
// not correct extension
return;
}
}
The attachment name can be found by
public static String getAttachmentName(Context ctxt, Uri contentUri) {
Cursor cursor = ctxt.getContentResolver().query(contentUri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
String res = "";
if (cursor != null){
cursor.moveToFirst();
int nameIdx = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
res = cursor.getString(nameIdx);
cursor.close();
}
return res;
}