Why ContentResolver does not see added files by another app? - android

I added files to Documents/MyExcelsFolder by using ContentResolver.insert and then also added new file to Documents/MyExcelsFolder folder by another app (for ex. FileManager)
Then I try to get all files from the MyExcelsFolder folder
fun getAppFiles(context: Context): List<AppFile> {
val appFiles = mutableListOf<AppFile>()
val contentResolver = context.contentResolver
val columns = mutableListOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
add(
MediaStore.MediaColumns.RELATIVE_PATH
)
}
}.toTypedArray()
val extensions = listOf("xls", "xlsx")
val mimes = extensions.map { MimeTypeMap.getSingleton().getMimeTypeFromExtension(it) }
val selection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
"${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
} else {
"${MediaStore.Images.Media.DATA} LIKE ?"
}
val selectionArgs = arrayOf(
"%${Environment.DIRECTORY_DOCUMENTS}/MyExcelsFolder%"
)
contentResolver.query(
MediaStore.Files.getContentUri("external"),
columns,
selection,
selectionArgs,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
)?.use { cursor ->
while (cursor.moveToNext()) {
val pathColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
val mimeColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)
val filePath = cursor.getString(pathColumnIndex)
val mimeType = cursor.getString(mimeColumnIndex)
if (mimeType != null && mimes.contains(mimeType)) {
// handle cursor
appFiles.add(cursor.toAppFile())
} else {
// need to check extension, because the Mime Type is null
val extension = File(filePath).extension
if (extensions.contains(extension)) {
// handle cursor
appFiles.add(cursor.toAppFile())
}
}
}
}
return appFiles
}
fun Cursor.toAppFile(): AppFile {
val cursor = this
val idColumnIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
val nameColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
val mimeColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)
val pathColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
val id = cursor.getLong(idColumnIndex)
val uri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id)
val fileDisplayName = cursor.getString(nameColumnIndex)
val filePath = cursor.getString(pathColumnIndex)
var mimeType = cursor.getString(mimeColumnIndex)
val relativePath = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.RELATIVE_PATH))
} else {
null
}
var type = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
if (type == null) {
type = File(filePath).extension
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(type)
}
return AppFile(
id = id,
uri = uri,
absolutePath = filePath,
name = fileDisplayName,
mimeType = mimeType,
extension = type,
relativePath = relativePath
)
}
And in result there are only files from ContentResolver added by insert command, and there no files copied by FileManager. How to see all files in cursor?
Operation system: Android 10 (Q) (API level 29)
Target API version: api 29

Starting from Android 10 there is a new storage access model in action which is called Scoped Storage and it is much more restrictive. In short:
Your app can always access its own directories.
Your app can write (with help of ContentResolver.insert) to the shared media collections and can read only files created by your app from them. You can access other apps files from these collections by requesting the READ_EXTERNAL_STORAGE permission.
Your app can access other files and directories by using file or directory system pickers.
It's a bit weird and looks like a bug that you are able to access xls files through the MediaStore.Files collection. The documentation says
The media store also includes a collection called MediaStore.Files.
Its contents depend on whether your app uses scoped storage, available
on apps that target Android 10 or higher:
If scoped storage is enabled, the collection shows only the photos,
videos, and audio files that your app has created.
If scoped storage
is unavailable or not being used, the collection shows all types of
media files.
But anyway you still are not able to access files created by other apps as stated above.
So depending on your use case there are a few options to go:
As accessing files through the MediaStore.Files works for you now, you can try to request READ_EXTERNAL_STORAGE permission as shown in this table to get a non-filtered access to media collections. But I would expect such way to work unreliably on different devices and/or expect to stop it working with the new updates, because the media collections are supposed to be used for media files only.
You can use ACTION_OPEN_DOCUMENT or ACTION_OPEN_DOCUMENT_TREE to show a file/directory picker to user and get access to a file or the whole directory tree. Please check the restrictions of this method also. I'd say this is the preferable way to go.
Android 10 allows you to temporarily opt-out from the scoped storage by using the android:requestLegacyExternalStorage flag. But Android 11 is out already and this flag does not have any effect in it.
You can request the new MANAGE_EXTERNAL_STORAGE permission and then request a special white-listing by user to access all files. That's how the file managers work now. This feature is available starting from Android 11, so you'll likely to use the opt-out flag for Android 10. Also be sure to check the restrictions and Google Play policies on using this feature if you are going to publish your app there.

Related

How do you use a Uri opened with ActivityResultContracts.OpenDocumentTree() and query the MediaStore for all audio in that folder?

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

Video path in Android 11 [duplicate]

According to the docs file path access is granted in Android R:
Starting in Android 11, apps that have the READ_EXTERNAL_STORAGE permission can read a device's media files using direct file paths and native libraries. This new capability allows your app to work more smoothly with third-party media libraries.
The problem is that I can't get the file path from MediaStore, so how are we supposed to read a file path that we can't access/retrieve? Is there a way, I'm not aware of, that we can get the file path from MediaStore?
Furthermore, the docs say the following:
All Files Access
Some apps have a core use case that requires broad file access, such as file management or backup & restore operations. They can get All Files Access by doing the following:
Declare the MANAGE_EXTERNAL_STORAGE permission.
Direct users to a system settings page where they can enable the Allow access to manage all files option for your app.
This permission grants the following:
Read access and write access to all files within shared storage.
Access to the contents of the MediaStore.Files table.
But I do not need all file access, I only want the user to select a video from MediaStore and pass the file path to FFmpeg(it requires a file path). I know that I can no longer use the _data column to retrieve a file path.
Please note:
I know a Uri is returned from MediaStore and does not point to a file.
I know that I can copy the file to my application directory and pass that to FFmpeg, but I could do that before Android R.
I can not pass FileDescriptor to FFmpeg and I can not use /proc/self/fd/ (I get /proc/7828/fd/70: Permission denied when selecting a file from the SD Card), have a look at this issue.
So what am I supposed to do, am I missing something? What was meant with can read a device's media files using direct file paths and native libraries?
After asking a question on issuetracker, I've come to the following conclusions:
On Android R, the File restrictions that were added in Android Q is removed. So we can once again access File objects.
If you are targeting Android 10 > and you want to access/use file paths, you will have to add/keep the following in your manifest:
android:requestLegacyExternalStorage="true"
This is to ensure that file paths are working on Android 10(Q). On Android R this attribute will be ignored.
Don't use DATA column for inserting or updating into Media Store, use DISPLAY_NAME and RELATIVE_PATH, here is an example:
ContentValues valuesvideos;
valuesvideos = new ContentValues();
valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "YourFolder");
valuesvideos.put(MediaStore.Video.Media.TITLE, "SomeName");
valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "SomeName");
valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1);
ContentResolver resolver = getContentResolver();
Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri uriSavedVideo = resolver.insert(collection, valuesvideos);
You can no longer use the ACTION_OPEN_DOCUMENT_TREE or the ACTION_OPEN_DOCUMENT intent action to request that the user select individual files from Android/data/,Android/obb/and all sub-directories.
It is recommended to only use File objects when you need to perform "seeking", like when using FFmpeg, for example.
You can only use the data column to access files that are on the disk. You should handle I/O Exceptions accordingly.
If you want to access a File or want a file path from a Uri that was returned from MediaStore, I've created a library that handles all the exceptions you might get. This includes all files on the disk, internal and removable disk. When selecting a File from Dropbox, for example, the File will be copied to your applications directory where you have full access, the copied file path will then be returned.
If you are targeting to Android 11 API, you cannot directly get access to the file paths, as there are many restrictions in API 30(Android R). As scoped storage API was introduced in Android 10(API 29), the storage is now divided into scoped storage (private storage) and shared storage (public storage). Scoped storage is a kind you can only have access to the files that are created in your scoped storage directory(i.e. /Android/data/ or /Android/media/<your-package-name>). You cannot access files from shared storage (i.e. internal storage/external SD card storage etc.)
The shared storage is again further divided into Media and Download collection. Media collection stores Image, Audio and Video files. Download collection would take care of non-media files.
To learn in more details about scoped storage and shared storage refer this link: Scoped Storage in Android 10 & Android 11 .
If you are dealing with Media files (i.e. Images, Videos, Audio) you can get the file path by Using Media Store API that having support to API 30(Android 11). and If you are dealing with non-media files(i.e. documents and other files) you can get the file path by using file Uri.
Note: If you are using the file or Uri util classes (such as RealPathUtil, FilePathUtils etc.) to get the file path, here you can get the desired file path but you cannot read that file, as it will throw an exception of Read Access (as Permission denied) in Android 11, as you cannot read the files that are created by another application.
So to achieve this scenario of getting the file path in Android 11(API 30), It a recommended to copy the file into the cache directory of your application using File Uri and get the path of the file access from cache directory.
Here in my scenario I have used both APIs to get the file access in Android 11. To get the file path of the media files (i.e. Images, Videos, Audio), I've used the Media Store API (Refer this link: Media Store API Example - Access media files from shared storage ), and to get the file path of the non-media files (i.e. Documents and other files), I've used fileDescriptor.
File Descriptor Example:
I have created the system dialog file picker to pick the file.
private fun openDocumentAction() {
val mimetypes = arrayOf(
"application/*", //"audio/*",
"font/*", //"image/*",
"message/*",
"model/*",
"multipart/*",
"text/*"
)
// you can customize the mime types as per your choice.
// Choose a directory using the system's file picker.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
//type = "application/pdf" //only pdf files
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimetypes)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
//putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, RC_SAF_NON_MEDIA)
}
And handled the result of file picker in onActivityResult method of the activity. Get the file URI at here.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RC_SAF_NON_MEDIA -> {
//document selection by SAF(Storage Access Framework) for Android 11
if (resultCode == RESULT_OK) {
// The result data contains a URI for the document or directory that
// the user selected.
data?.data?.also { uri ->
//Permission needed if you want to retain access even after reboot
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Perform operations on the document using its URI.
val path = makeFileCopyInCacheDir(uri)
Log.e(localClassName, "onActivityResult: path ${path.toString()} ")
}
}
}
}
}
Pass the file URI to the below method to get the file path. This method will create a file object at cache directory of your application and from that location you can easily get Read access to that file.
private fun makeFileCopyInCacheDir(contentUri :Uri) : String? {
try {
val filePathColumn = arrayOf(
//Base File
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.DISPLAY_NAME,
//Normal File
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.DISPLAY_NAME
)
//val contentUri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(mediaUrl))
val returnCursor = contentUri.let { contentResolver.query(it, filePathColumn, null, null, null) }
if (returnCursor!=null) {
returnCursor.moveToFirst()
val nameIndex = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
val name = returnCursor.getString(nameIndex)
val file = File(cacheDir, name)
val inputStream = contentResolver.openInputStream(contentUri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()
//int bufferSize = 1024;
val bufferSize = Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
return file.absolutePath
}
} catch (ex: Exception) {
Log.e("Exception", ex.message!!)
}
return contentUri.let { UriPathUtils().getRealPathFromURI(this, it).toString() }
}
Note: You can use this method to get file path for both media files (Images, Videos, Audio) and non-media files (Documents and other files) as well. Just need to pass a file Uri.
For getting path, i'm coping file with fileDescriptor to new path & i use that path.
Finding File Name:
private static String copyFileAndGetPath(Context context, Uri realUri, String id) {
final String selection = "_id=?";
final String[] selectionArgs = new String[]{id};
String path = null;
Cursor cursor = null;
try {
final String[] projection = {"_display_name"};
cursor = context.getContentResolver().query(realUri, projection, selection, selectionArgs,
null);
cursor.moveToFirst();
final String fileName = cursor.getString(cursor.getColumnIndexOrThrow("_display_name"));
File file = new File(context.getCacheDir(), fileName);
FileUtils.saveAnswerFileFromUri(realUri, file, context);
path = file.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return path;
}
Copy With File Descriptor:
fun saveAnswerFileFromUri(uri: Uri, destFile: File?, context: Context) {
try {
val pfd: ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, "r")!!
if (pfd != null) {
val fd: FileDescriptor = pfd.getFileDescriptor()
val fileInputStream: InputStream = FileInputStream(fd)
val fileOutputStream: OutputStream = FileOutputStream(destFile)
val buffer = ByteArray(1024)
var length: Int
while (fileInputStream.read(buffer).also { length = it } > 0) {
fileOutputStream.write(buffer, 0, length)
}
fileOutputStream.flush()
fileInputStream.close()
fileOutputStream.close()
pfd.close()
}
} catch (e: IOException) {
Timber.w(e)
}
}

Can we delete an image file using MediaStore API? if yes then how

I have a requirement to delete screenshot image file after a certain time using background service in my app and it was working fine using the above method
private void deleteTheFile(String path) {
File fdelete = new File(path);
if (fdelete.exists()) {
if (fdelete.delete()) {
getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(path))));
Log.i(TAG, "deleteTheFile: file deleted");
} else {
Log.i(TAG, "deleteTheFile: file not dellleeettteeeddd");
}
}
But as everyone knows about the changes which came with android R (11)
So I tried to update my app with
MANAGE_EXTERNAL_STORAGE permission
But Google rejected my update saying
Issue: Need to use Media Store API
You have requested access to All Files Access permission but it
appears that your app's core feature requires access to only Media
Files. With the MediaStore API, apps can contribute and access media
that's available on an external storage volume without the need for
the access all files permission.
Please update your app so that the feature uses Media Store APIs and
remove All Files Access (MANAGE_EXTERNAL_STORAGE) permission.
But I have never worked with media store API before and I don't know can it delete an image file with it, because deleting a file comes under writeable section
Using createDeleteRequest
private fun deleteImages(uris: List<Uri>) {
val pendingIntent = MediaStore.createDeleteRequest(contentResolver, uris.filter {
checkUriPermission(it, Binder.getCallingPid(), Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PackageManager.PERMISSION_GRANTED
})
startIntentSenderForResult(pendingIntent.intentSender, REQ_CODE, null, 0, 0, 0)
}
using contentResolver
// Remove a specific media item.
val resolver = applicationContext.contentResolver
// URI of the image to remove.
val imageUri = "..."
// WHERE clause.
val selection = "..."
val selectionArgs = "..."
// Perform the actual removal.
val numImagesRemoved = resolver.delete(
imageUri,
selection,
selectionArgs)
https://github.com/android/storage-samples/tree/main/MediaStore
This is an android official sample you can follow to have an understanding and try to implement it using MediaStoreAPI
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pendingIntent = MediaStore.createDeleteRequest(context.contentResolver, getImageDeleteUri(context, filePath))
context.startIntentSenderForResult(pendingIntent.intentSender, requestCode, null, 0, 0, 0)}
//finally handle it's result in onActivityResult
getting delete uri from image path:
fun getImageDeleteUri(context: Context, path: String): Uri? {
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + " = ?",
arrayOf(path),
null
)
val uri = if (cursor != null && cursor.moveToFirst())
ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
) else null
cursor?.close()
return uri
}

How do some apps reach the contents of ".../Android/..." sub-folders on Android 11 without root?

Background
There are various storage restrictions on Android 10 and 11, which also includes a new permission (MANAGE_EXTERNAL_STORAGE) to access all files (yet it doesn't allow access to really all files ) while the previous storage permission got reduced to grant access just to media files :
Apps can reach the "media" sub folder freely.
Apps can never reach "data" sub folder and especially the content.
For "obb" folder, if the app was allowed to install apps, it can reach it (to copy files to there). Otherwise it can't.
Using USB or root, you could still reach them, and as an end user you can reach them via the built-in file-manager app "Files".
The problem
I've noticed an app that somehow overcome this limitation (here) called "X-plore": Once you enter "Android/data" folder, it asks you to grant access to it (directly using SAF, somehow), and when you grant it, you can access everything in all folders of "Android" folder.
This means there might still be a way to reach it, but problem is that I couldn't make a sample that does the same, for some reason.
What I've found and tried
It seems this app targets API 29 (Android 10), and that it doesn't use the new permission yet, and that it has the flag requestLegacyExternalStorage. I don't know if the same trick they use will work when targeting API 30, but I can say that on my case, running on Pixel 4 with Android 11, it works fine.
So I tried to do the same:
I made a sample POC that targets Android API 29, has storage permissions (of all kinds) granted, including the legacy flag.
I tried to request access directly to "Android" folder (based on here), which sadly didn't work as it goes to some reason (kept going to DCIM folder, no idea why) :
val androidFolderDocumentFile = DocumentFile.fromFile(File(primaryVolume.directory!!, "Android"))
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidFolderDocumentFile.uri)
startActivityForResult(intent, 1)
I tried various flags combinations.
When launching the app, when I reach the "Android" folder myself manually as this didn't work well, and I granted the access to this folder just like on the other app.
When getting the result, I try to fetch the files and folders in the path, but it fails to get them:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AppLog", "resultCode:$resultCode")
val uri = data?.data ?: return
if (!DocumentFile.isDocumentUri(this, uri))
return
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val fullPathFromTreeUri = FileUtilEx.getFullPathFromTreeUri(this, uri) // code for this here: https://stackoverflow.com/q/56657639/878126
val documentFile = DocumentFile.fromTreeUri(this, uri)
val listFiles: Array<DocumentFile> = documentFile!!.listFiles() // this returns just an array of a single folder ("media")
val androidFolder = File(fullPathFromTreeUri)
androidFolder.listFiles()?.forEach {
Log.d("AppLog", "${it.absoluteFile} children:${it.listFiles()?.joinToString()}") //this does find the folders, but can't reach their contents
}
Log.d("AppLog", "granted uri:$uri $fullPathFromTreeUri")
}
So using DocumentFile.fromTreeUri I could still get just "media" folder which is useless, and using the File class I could only see there are also "data" and "obb" folders, but still couldn't reach their contents...
So this didn't work well at all.
Later I've found out another app that uses this trick, called "MiXplorer". On this app, it failed to request "Android" folder directly (maybe it didn't even try), but it does grant you full access to it and its sub-folders once you allow it. And, it targets API 30, so this means it's not working just because you target API 29.
I've noticed (someone wrote me) that with some changes to the code, I could request access to each of the sub-folders separately (meaning a request for "data" and a new request for "obb"), but this is not what I see here, that apps do.
Meaning, to get to "Android" folder, I get use this Uri as a parameter for Intent.EXTRA_INITIAL_URI :
val androidUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android").build()
However, once you get an access to it, you won't be able to get the list of files from it, not via File, and not via SAF.
But, as I wrote, the weird thing is that if you try something similar, of getting to "Android/data" instead, you will be able to get its content:
val androidDataUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android/data").build()
The questions
How can I request an Intent directly to "Android" folder that will actually let me access to it, and let me get the sub-folders and their contents?
Is there another alternative for this? Maybe using adb and/or root, I could grant SAF access to this specific folder ?
Here is how it works in X-plore:
When on Build.VERSION.SDK_INT>=30,
[Internal storage]/Android/data is not accessible, java File.canRead() or File.canWrite() returns false, so we need to switch to alternative file system for files inside of this folder (and possibly also obb).
You already know how Storage access framework works, so I'll just give details about what needs to be done exactly.
You call ContentResolver.getPersistedUriPermissions() to find out if you already have saved permission for this folder. Initially you don't have it, so you ask user for permission:
To request access, use startActivityForResult with Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android"))
Here you set with EXTRA_INITIAL_URI that picker shall start directly on Android folder on primary storage, because we want access to Android folder. When your app will target API30, picker won't allow to choose root of storage, and also by getting permission to Android folder, you can work with both data and obb folders inside, with one permission request.
When user confirms by 2 clicks, in onActivityResult you'll get Uri in data which should be content://com.android.externalstorage.documents/tree/primary%3AAndroid. Make needed checks to verify that user confirmed correct folder. Then call contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) to save permission, and you're ready.
So we're back to ContentResolver.getPersistedUriPermissions(), which contains list of granted permissions (there may be more of them), the one you've granted above looks like this: UriPermission {uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid, modeFlags=3} (same Uri as you got in onActivityResult). Iterate the list from getPersistedUriPermissions to find uri of interest, if found work with it, otherwise ask user for grant.
Now you want to work with ContentResolver and DocumentsContract using this "tree" uri and your relative path to files inside of Android folder. Here is example to list data folder:
data/ is path relative to granted "tree" uri. Build final uri using either DocumentsContract.buildChildDocumentsUriUsingTree() (to list files) or DocumentsContract.buildDocumentUriUsingTree() (for working with individual files), example: DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri), DocumentsContract.getTreeDocumentId(treeUri)+"/data/"), you'll get uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid/document/primary%3AAndroid%2Fdata%2F/children suitable for listing files in data folder. Now call ContentResolver.query(uri, ...) and process data in Cursor to get folder listing.
Similar way you work with other SAF functionality to read/write/rename/move/delete/create, which you probably already know, using ContentResolver or methods of DocumentsContract.
Some details:
it doesn't need android.permission.MANAGE_EXTERNAL_STORAGE
it works on target API 29 or 30
it works only on primary storage, not for external SD cards
for all files inside of data folder, you need to use SAF (java File won't work), just use file hierarchy relative to Android folder
in future Google may patch this hole in their "security" intentions, and this may not work after some security update
EDIT: sample code, based on Cheticamp Github sample. The sample shows the content (and file-count) of each of the sub-folders of "Android" folder:
class MainActivity : AppCompatActivity() {
private val handleIntentActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK)
return#registerForActivityResult
val directoryUri = it.data?.data ?: return#registerForActivityResult
contentResolver.takePersistableUriPermission(
directoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
if (checkIfGotAccess())
onGotAccess()
else
Log.d("AppLog", "you didn't grant permission to the correct folder")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val openDirectoryButton = findViewById<FloatingActionButton>(R.id.fab_open_directory)
openDirectoryButton.setOnClickListener {
openDirectory()
}
}
private fun checkIfGotAccess(): Boolean {
return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
}
private fun onGotAccess() {
Log.d("AppLog", "got access to Android folder. showing content of each folder:")
#Suppress("DEPRECATION")
File(Environment.getExternalStorageDirectory(), "Android").listFiles()?.forEach { androidSubFolder ->
val docId = "$ANDROID_DOCID/${androidSubFolder.name}"
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(androidTreeUri, docId)
val contentResolver = this.contentResolver
Log.d("AppLog", "content of:${androidSubFolder.absolutePath} :")
contentResolver.query(childrenUri, null, null, null)
?.use { cursor ->
val filesCount = cursor.count
Log.d("AppLog", "filesCount:$filesCount")
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val mimeIndex = cursor.getColumnIndex("mime_type")
while (cursor.moveToNext()) {
val displayName = cursor.getString(nameIndex)
val mimeType = cursor.getString(mimeIndex)
Log.d("AppLog", " $displayName isFolder?${mimeType == DocumentsContract.Document.MIME_TYPE_DIR}")
}
}
}
}
private fun openDirectory() {
if (checkIfGotAccess())
onGotAccess()
else {
val primaryStorageVolume = (getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
val intent =
primaryStorageVolume.createOpenDocumentTreeIntent().putExtra(EXTRA_INITIAL_URI, androidUri)
handleIntentActivityResult.launch(intent)
}
}
companion object {
private const val ANDROID_DOCID = "primary:Android"
private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
private val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
}
Well, I tried this code and it works on Android API 29, Samsung Galaxy 20FE:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void triggerStorageAccessFramework() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_STORAGE_ACCESS) {
Uri treeUri = null;
// Get Uri from Storage Access Framework.
treeUri = data.getData();
// Persist URI in shared preference so that you can use it later.
// Use your own framework here instead of PreferenceUtil.
MySharedPreferences.getInstance(null).setFileURI(treeUri);
// Persist access permissions.
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
createDir(DIR_PATH);
finish();
}
}
private void createDir(String path) {
Uri treeUri = MySharedPreferences.getInstance(null).getFileURI();
if (treeUri == null) {
return;
}
// start with root of SD card and then parse through document tree.
DocumentFile document = DocumentFile.fromTreeUri(getApplicationContext(), treeUri);
document.createDirectory(path);
}
I'm calling this from a button onClick:
Button btnLinkSd = findViewById(R.id.btnLinkSD);
btnLinkSd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
triggerStorageAccessFramework();
}
});
In the UI, I'm pressing "show internal storage", I navigate to Android directory and press allow. After that, in debugging, if I try to list all files under android I'm getting a list of all directories in Data. If that's what you are looking for.
And finally, results in debug:
"Java Version Tested on Android 11"
This will copy file from assets folder to any directory inside android/data/xxx
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends AppCompatActivity
{
private final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
private final String ANDROID_DOCID =
"primary:Android/data/xxxxFolderName";
Uri uri;
Uri treeUri;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b=findViewById(R.id.ok);
uri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
ANDROID_DOCID
);
treeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
ANDROID_DOCID
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
openDirectory();
}
}
private Boolean checkIfGotAccess() {
List<UriPermission> permissionList = getContentResolver().getPersistedUriPermissions();
for (int i = 0; i < permissionList.size(); i++) {
UriPermission it = permissionList.get(i);
if (it.getUri().equals(treeUri) && it.isReadPermission())
return true;
}
return false;
}
#RequiresApi(api = Build.VERSION_CODES.Q)
private void openDirectory() {
if (checkIfGotAccess()) {
copyFile(treeUri);
//return;
}
Intent intent =
getPrimaryVolume().createOpenDocumentTreeIntent()
.putExtra(EXTRA_INITIAL_URI, uri);
ActivityResultLauncher<Intent> handleIntentActivityResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
if (result.getData() == null || result.getData().getData() == null)
return;
Uri directoryUri = result.getData().getData();
getContentResolver().takePersistableUriPermission(
directoryUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
);
if (checkIfGotAccess())
copyFile(treeUri);
else
Log.d("AppLog", "you didn't grant permission to the correct folder");
}
});
handleIntentActivityResult.launch(intent);
}
#RequiresApi(api = Build.VERSION_CODES.N)
private StorageVolume getPrimaryVolume() {
StorageManager sm = (StorageManager) getSystemService(STORAGE_SERVICE);
return sm.getPrimaryStorageVolume();
}
private void copyFile(Uri treeUri) {
AssetManager assetManager = getAssets();
OutputStream out;
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
String extension = "ini";
try {
InputStream inn = assetManager.open("xxxxfileName.ini");
assert pickedDir != null;
DocumentFile existing = pickedDir.findFile("xxxxfileName.ini");
if(existing!=null)
existing.delete();
DocumentFile newFile = pickedDir.createFile("*/" + extension, "EnjoyCJZC.ini");
assert newFile != null;
out = getContentResolver().openOutputStream(newFile.getUri());
byte[] buffer = new byte[1024];
int read;
while ((read = inn.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
inn.close();
out.flush();
out.close();
} catch (Exception fnfe1) {
fnfe1.printStackTrace();
}
}
}

Android 11 (R) file path access

According to the docs file path access is granted in Android R:
Starting in Android 11, apps that have the READ_EXTERNAL_STORAGE permission can read a device's media files using direct file paths and native libraries. This new capability allows your app to work more smoothly with third-party media libraries.
The problem is that I can't get the file path from MediaStore, so how are we supposed to read a file path that we can't access/retrieve? Is there a way, I'm not aware of, that we can get the file path from MediaStore?
Furthermore, the docs say the following:
All Files Access
Some apps have a core use case that requires broad file access, such as file management or backup & restore operations. They can get All Files Access by doing the following:
Declare the MANAGE_EXTERNAL_STORAGE permission.
Direct users to a system settings page where they can enable the Allow access to manage all files option for your app.
This permission grants the following:
Read access and write access to all files within shared storage.
Access to the contents of the MediaStore.Files table.
But I do not need all file access, I only want the user to select a video from MediaStore and pass the file path to FFmpeg(it requires a file path). I know that I can no longer use the _data column to retrieve a file path.
Please note:
I know a Uri is returned from MediaStore and does not point to a file.
I know that I can copy the file to my application directory and pass that to FFmpeg, but I could do that before Android R.
I can not pass FileDescriptor to FFmpeg and I can not use /proc/self/fd/ (I get /proc/7828/fd/70: Permission denied when selecting a file from the SD Card), have a look at this issue.
So what am I supposed to do, am I missing something? What was meant with can read a device's media files using direct file paths and native libraries?
After asking a question on issuetracker, I've come to the following conclusions:
On Android R, the File restrictions that were added in Android Q is removed. So we can once again access File objects.
If you are targeting Android 10 > and you want to access/use file paths, you will have to add/keep the following in your manifest:
android:requestLegacyExternalStorage="true"
This is to ensure that file paths are working on Android 10(Q). On Android R this attribute will be ignored.
Don't use DATA column for inserting or updating into Media Store, use DISPLAY_NAME and RELATIVE_PATH, here is an example:
ContentValues valuesvideos;
valuesvideos = new ContentValues();
valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "YourFolder");
valuesvideos.put(MediaStore.Video.Media.TITLE, "SomeName");
valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "SomeName");
valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1);
ContentResolver resolver = getContentResolver();
Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri uriSavedVideo = resolver.insert(collection, valuesvideos);
You can no longer use the ACTION_OPEN_DOCUMENT_TREE or the ACTION_OPEN_DOCUMENT intent action to request that the user select individual files from Android/data/,Android/obb/and all sub-directories.
It is recommended to only use File objects when you need to perform "seeking", like when using FFmpeg, for example.
You can only use the data column to access files that are on the disk. You should handle I/O Exceptions accordingly.
If you want to access a File or want a file path from a Uri that was returned from MediaStore, I've created a library that handles all the exceptions you might get. This includes all files on the disk, internal and removable disk. When selecting a File from Dropbox, for example, the File will be copied to your applications directory where you have full access, the copied file path will then be returned.
If you are targeting to Android 11 API, you cannot directly get access to the file paths, as there are many restrictions in API 30(Android R). As scoped storage API was introduced in Android 10(API 29), the storage is now divided into scoped storage (private storage) and shared storage (public storage). Scoped storage is a kind you can only have access to the files that are created in your scoped storage directory(i.e. /Android/data/ or /Android/media/<your-package-name>). You cannot access files from shared storage (i.e. internal storage/external SD card storage etc.)
The shared storage is again further divided into Media and Download collection. Media collection stores Image, Audio and Video files. Download collection would take care of non-media files.
To learn in more details about scoped storage and shared storage refer this link: Scoped Storage in Android 10 & Android 11 .
If you are dealing with Media files (i.e. Images, Videos, Audio) you can get the file path by Using Media Store API that having support to API 30(Android 11). and If you are dealing with non-media files(i.e. documents and other files) you can get the file path by using file Uri.
Note: If you are using the file or Uri util classes (such as RealPathUtil, FilePathUtils etc.) to get the file path, here you can get the desired file path but you cannot read that file, as it will throw an exception of Read Access (as Permission denied) in Android 11, as you cannot read the files that are created by another application.
So to achieve this scenario of getting the file path in Android 11(API 30), It a recommended to copy the file into the cache directory of your application using File Uri and get the path of the file access from cache directory.
Here in my scenario I have used both APIs to get the file access in Android 11. To get the file path of the media files (i.e. Images, Videos, Audio), I've used the Media Store API (Refer this link: Media Store API Example - Access media files from shared storage ), and to get the file path of the non-media files (i.e. Documents and other files), I've used fileDescriptor.
File Descriptor Example:
I have created the system dialog file picker to pick the file.
private fun openDocumentAction() {
val mimetypes = arrayOf(
"application/*", //"audio/*",
"font/*", //"image/*",
"message/*",
"model/*",
"multipart/*",
"text/*"
)
// you can customize the mime types as per your choice.
// Choose a directory using the system's file picker.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
//type = "application/pdf" //only pdf files
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimetypes)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
//putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, RC_SAF_NON_MEDIA)
}
And handled the result of file picker in onActivityResult method of the activity. Get the file URI at here.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RC_SAF_NON_MEDIA -> {
//document selection by SAF(Storage Access Framework) for Android 11
if (resultCode == RESULT_OK) {
// The result data contains a URI for the document or directory that
// the user selected.
data?.data?.also { uri ->
//Permission needed if you want to retain access even after reboot
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Perform operations on the document using its URI.
val path = makeFileCopyInCacheDir(uri)
Log.e(localClassName, "onActivityResult: path ${path.toString()} ")
}
}
}
}
}
Pass the file URI to the below method to get the file path. This method will create a file object at cache directory of your application and from that location you can easily get Read access to that file.
private fun makeFileCopyInCacheDir(contentUri :Uri) : String? {
try {
val filePathColumn = arrayOf(
//Base File
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.DISPLAY_NAME,
//Normal File
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.DISPLAY_NAME
)
//val contentUri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(mediaUrl))
val returnCursor = contentUri.let { contentResolver.query(it, filePathColumn, null, null, null) }
if (returnCursor!=null) {
returnCursor.moveToFirst()
val nameIndex = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
val name = returnCursor.getString(nameIndex)
val file = File(cacheDir, name)
val inputStream = contentResolver.openInputStream(contentUri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()
//int bufferSize = 1024;
val bufferSize = Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
return file.absolutePath
}
} catch (ex: Exception) {
Log.e("Exception", ex.message!!)
}
return contentUri.let { UriPathUtils().getRealPathFromURI(this, it).toString() }
}
Note: You can use this method to get file path for both media files (Images, Videos, Audio) and non-media files (Documents and other files) as well. Just need to pass a file Uri.
For getting path, i'm coping file with fileDescriptor to new path & i use that path.
Finding File Name:
private static String copyFileAndGetPath(Context context, Uri realUri, String id) {
final String selection = "_id=?";
final String[] selectionArgs = new String[]{id};
String path = null;
Cursor cursor = null;
try {
final String[] projection = {"_display_name"};
cursor = context.getContentResolver().query(realUri, projection, selection, selectionArgs,
null);
cursor.moveToFirst();
final String fileName = cursor.getString(cursor.getColumnIndexOrThrow("_display_name"));
File file = new File(context.getCacheDir(), fileName);
FileUtils.saveAnswerFileFromUri(realUri, file, context);
path = file.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return path;
}
Copy With File Descriptor:
fun saveAnswerFileFromUri(uri: Uri, destFile: File?, context: Context) {
try {
val pfd: ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, "r")!!
if (pfd != null) {
val fd: FileDescriptor = pfd.getFileDescriptor()
val fileInputStream: InputStream = FileInputStream(fd)
val fileOutputStream: OutputStream = FileOutputStream(destFile)
val buffer = ByteArray(1024)
var length: Int
while (fileInputStream.read(buffer).also { length = it } > 0) {
fileOutputStream.write(buffer, 0, length)
}
fileOutputStream.flush()
fileInputStream.close()
fileOutputStream.close()
pfd.close()
}
} catch (e: IOException) {
Timber.w(e)
}
}

Categories

Resources