I'm trying to crop media images from MediaStore query,but got this Execption:
Caused by: java.lang.SecurityException: UID 10160 does not have permission to content://media/external/images/media/48 [user 0]
at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
at android.os.Parcel.createException(Parcel.java:2409)
at android.os.Parcel.readException(Parcel.java:2392)
at android.os.Parcel.readException(Parcel.java:2334)
at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:2326)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1758)
at android.app.Activity.startActivityForResult(Activity.java:5407)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:588)
at android.app.Activity.startActivityForResult(Activity.java:5365)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:574)
As I completely understand it's permission related problem, yet I have no idea how to fix it.As far as I can see,the Crop-App has no read permission to [content://media/external/images/media/48],which my-own-app hold.
the code cause this problem as below:
val su = srcUri ?: throw IllegalArgumentException("Source uri is Null")
val f = if (path.isNullOrBlank()) {
File(
FileTool.getAppCacheDir(act),
tempCropName()
).also { path = it.absolutePath }
} else {
File(path!!)
}
val cropUri =
FileTool.getFileUri(act, f) ?: throw IllegalArgumentException("Failed to get crop uri")
val intent = Intent("com.android.camera.action.CROP").also {
it.setDataAndType(su, MediaType.IMAGE.value())
it.putExtra("aspectX", 1)//ratio
it.putExtra("aspectY", 1)
it.putExtra("outputX", size)//size
it.putExtra("outputY", size)
it.putExtra("scale", true)
it.putExtra("return-data", false)//no thumbnail got from back intent
it.putExtra("outputFormat", format)
it.putExtra(MediaStore.EXTRA_OUTPUT, cropUri)
}
and the SecurityException located at it.setDataAndType(su, MediaType.IMAGE.value()).
as for su,the source uri,got like this
private fun loadImageUriList(
bucketId: Long,
context:Context
): List<Uri>? {
val selection = "${MediaStore.Images.Media.BUCKET_ID} = ?"
val sort = "${MediaStore.Images.Media._ID} DESC"
val selectionArgs = arrayOf(bucketId.toString())
val images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val c = context.contentResolver.query(images, null, selection, selectionArgs, sort)?:return null
val imageUris = arrayListOf<Uri>()
try {
if (c.moveToFirst()) {
val iid = c.getColumnIndex(MediaStore.MediaColumns._ID)
do {
val imgId = c.getInt(iid)
val path = Uri.withAppendedPath(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imgId.toString()
)
imageUris.add(path)
} while (c.moveToNext())
}
} finally {
c.closeQuietly()
}
return imageUris
}
)
I tried
MediaStore.getRedactedUri(resolver,su)
but the problem still ocurred.
by the way, crop image from the system camera worked fine.
Now,I have to copy the image from source Uri to my app's directory,then do the crop job(the Intent above).it's so much work to do and ugly,I know I must have missed something,but I look through google developer doc,got nothing.
please ,help.
Related
I want to get content (images & videos) of user selected folder. Code to select the folder is:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
or Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
startActivityForResult(intent, 1088)
And onActivityResult, I am persisting the permission(even I tested without restarting the device but it is not working):
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {
if (requestCode == 1088) {
val selectedDirUri = data!!.data
grantUriPermission(packageName, selectedDirUri, (Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION))
val takeFlags = (data.flags
and (Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
contentResolver.takePersistableUriPermission(selectedDirUri!!, takeFlags)
}
}
}
And I am traversing through all files using:
val rootUri: Uri = Uri.parse(selectedDir)
val contentResolver: ContentResolver = context.contentResolver
var childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri))
val dirNodes: MutableList<Uri> = LinkedList()
dirNodes.add(childrenUri)
while (!dirNodes.isEmpty()) {
childrenUri = dirNodes.removeAt(0) // get the item from top
val c = contentResolver.query(childrenUri,
arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_LAST_MODIFIED),
null, null, null)
try {
while (c!!.moveToNext()) {
val docId = c.getString(0)
val fileName = c.getString(1)
val mimeType = c.getString(2)
val lastModified = c.getLong(3)
if (isDirectory(mimeType)) {
val newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId)
dirNodes.add(newNode)
} else {
val newNode = DocumentsContract.buildDocumentUriUsingTree(rootUri, docId)
Logger.log("TAG", "========1 Images: $newNode")
val ***sourceFileUri*** = newNode.toString()
}
}
} finally {
closeQuietly(c)
}
}
Then I display images and videos using Glide, it is not displaying.
Even if I try to copy the image using below code:
val inputStream: InputStream? = context.contentResolver.openInputStream(***sourceFileUri***)
I am getting below error, the above line gives an error:
java.lang.SecurityException: com.android.externalstorage has no access
to content://media/external _primary/file/1000008384 at
android.os.Parcel.createException or Null(Parcel.java:2438) at 08
android.os.Parcel.createException(P arcel.java:2422) at
android.os.Parcel.readException(Par cel.java:2405) at
android.database.DatabaseUtils.rea dExceptionFromParcel(DatabaseUtil
s.java:190) at android.database.DatabaseUtils.rea dException
WithFileNotFoundExcepti on From Parcel(DatabaseUtils.java:15 3)
This is happening in Vivo, Oppo and specially in new Samsung phones only which has android OS 11 and 12. I am really frustrated, I tried all possible solution but not able to find any solution till now.
Any solution or advice would be really helpful and appreciated, please please help me.
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 keep getting an empty cursor with this method used for loading external images, no idea why. It also worked for me on one emulator configuration before, but now when I try to Log the contents of ID it says:
android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 0. There's pictures installed on the emulator in 3 different external directories.
private suspend fun loadImages(): List<Image>
{
return withContext(Dispatchers.IO) {
val uri = if(Build.VERSION.SDK_INT >= 29) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else MediaStore.Images.Media.EXTERNAL_CONTENT_URI
requireActivity().contentResolver.query(uri, arrayOf(MediaStore.Images.Media._ID),
null, null, "${MediaStore.Images.Media.DATE_ADDED} ASC"
)?.use { cursor ->
val photos = mutableListOf<Image>()
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while(cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
photos.add(Image(id, contentUri))
}
photos
} ?: listOf()
}
}
pic of working image gallery
As usual with my issues, it was something entirely different. The emulator bugged out, I reset it and uploaded images again and it works now. The code is a bit changed without the dispatcher now.
private fun loadImages(): List<Image>
{
val photos = mutableListOf<Image>()
val uri = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else MediaStore.Images.Media.EXTERNAL_CONTENT_URI
requireActivity().contentResolver.query(uri, arrayOf(MediaStore.Images.Media._ID),
null, null, null
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
while(cursor.moveToNext()) {
photos.add(Image(ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(idColumn))))
}
return photos.toList()
} ?: return listOf()
}
this is the code now, it works fine
My current Android Application stores pdf files on external storage using
val contentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL)
The application creates a sub folder in the standard Documents folder.
My manifest contains
android:requestLegacyExternalStorage = true
For Android 30 I request the following
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
I have a background worker that attempts to clear all down loaded files, the code I employ is as follows:-
#RequiresApi(Build.VERSION_CODES.Q)
override suspend fun doActualFlowedWork(): Result {
if (hasSdkHigherThan(Build.VERSION_CODES.P)) {
clearDownloadFiles()
} else {
clearDownloadLegacyFiles()
}
return result ?: Result.success()
}
#Suppress("BlockingMethodInNonBlockingContext")
#RequiresApi(Build.VERSION_CODES.Q)
private fun clearDownloadFiles() {
val resolver = context.contentResolver
val relativeLocation = "${Environment.DIRECTORY_DOCUMENTS}${MY_SUB_FOLDER}"
val contentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL)
resolver.query(
contentUri,
arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.RELATIVE_PATH),
"${MediaStore.MediaColumns.RELATIVE_PATH}=?",
arrayOf(relativeLocation),
null
).use { cursor ->
cursor?.let {
while (it.moveToNext()) {
val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
val displayNameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
val relativePathNameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.RELATIVE_PATH)
if (cursor.getString(relativePathNameIndex) == relativeLocation) {
val fileContentUri = MediaStore.Files.getContentUri(VOLUME_NAME_EXTERNAL, cursor.getLong(idIndex))
val count = context.contentResolver.delete(fileContentUri, null, null)
if (count == 0) Timber.e("FAILED to clear downloaded file = ${cursor.getString(displayNameIndex)}")
else Timber.i("Cleared downloaded file = ${cursor.getString(displayNameIndex)}")
}
}
}
}
}
#Suppress("DEPRECATION")
private fun clearDownloadLegacyFiles() {
val documentsFolder = File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOCUMENTS)
if (!documentsFolder.exists()) return
val mendeleyLiteSubFolder = File(Environment.getExternalStorageDirectory(), "${Environment.DIRECTORY_DOCUMENTS}$MY_SUB_FOLDER")
if (!mendeleyLiteSubFolder.exists()) return
val downloadFiles = mendeleyLiteSubFolder.listFiles()
downloadFiles?.forEach { downloadFile ->
if (downloadFile.exists()) downloadFile.delete()
Timber.i("Clearing downloaded file = $downloadFile")
}
}
This clear down worker completes OK, with the logs showing the files have been deleted
however when I use Android Studio Device File Explorer to view my Document sub folder the physical pdf files are still present.
Are my expectations incorrect?
What does this code achieve context.contentResolver.delete(fileContentUri, null, null)?
How do I delete physical files from my sub folder?
I have searched high and low and not found an answer to my particular question, I hope someone can help.
I am developing this for Android 9 and above, the code I use for older releases works fine.
It's quite simple, I have stored an image in the MediaStore, I have found the image in the media store, I return its path, I check the path exists and it does, it also has a correct size and is visible in the android Gallery. So why when I try to open it with
val bitmap2 = BitmapFactory.decodeFile(fullPath)
bitmap2 comes back as null - no errors are generated by the above command. The parseAllImages function was taken from the web and tweaked slightly but seems to work ok as far as I can tell.
sample code
private fun setPic() {
if (mediaPath.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val content = GalleryAdd.parseAllImages(requireActivity(), mediaPath)
val fullPath = content
if (File(fullPath).exists()) {
val tester = File(fullPath).length()
val bitmap2 = BitmapFactory.decodeFile(fullPath)
viewModel.setBitmap(bitmap2)
}
}
}
}
fun parseAllImages(act : Activity, name : String) : String {
try {
val projection =
arrayOf(MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID)
val cursor = act.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, // Which columns to return
null, // Return all rows
null,
null
)
val size: Int = cursor!!.getCount()
/******* If size is 0, there are no images on the SD Card. */
if (size == 0) {
} else {
val thumbID = 0
if (cursor != null) {
while (cursor.moveToNext()) {
val file_ColumnIndex: Int =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
/**************** Captured image details */
/***** Used to show image on view in LoadImagesFromSDCard class */
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val path: String = cursor.getString(file_ColumnIndex)
val fileName =
path.substring(path.lastIndexOf("/") + 1, path.length)
if (fileName == name)
{
return path
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
Code snippet I use to write to the media store
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = activity.contentResolver
val contentValues = ContentValues()
contentValues.put(
MediaStore.MediaColumns.DISPLAY_NAME,
fileName
)
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.separator + "fishy"
)
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
val tester = imageUri.toString() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + "fishy" + File.separator + fileName
scanLoc = fileName
fos = resolver.openOutputStream(imageUri!!)!!
val file = File(currentPhotoPath)
val ins: InputStream = file.inputStream()
ins.copyTo(fos)
}
Any help or can someone point me to sample code that can read a jpg image from the mediastore given it's name? It's not production ready so please forgive lack of error checks.
Thanks
Lee.