I'm trying to retrieve URI from gallery and camera in one place (as record in Room database), therefore i need to standardize the access between them. I get it done for the camera by create a file and then retrieve its URI. The URI looks like this:
// first URI
content://media/external_primary/images/media/45
It works and i can retrieve the image based on that URI after reopening the app.
Here is the code for camera
private val launcherIntentCamera = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val bitmap = result?.data?.extras?.get("data") as Bitmap
val uri = savePhotoToExternalStorage(
requireActivity().contentResolver,
UUID.randomUUID().toString(),
bitmap
)
uri?.let {
viewModel.updateProfilePic(it.toString())
}
}
}
private fun startTakePhoto() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
launcherIntentCamera.launch(intent)
}
And then i try for gallery intent and what i receive is this:
// second URI
content://com.android.providers.media.documents/document/image%3A44
Based on what i observe, this URI is temporary. And when i try to retrieve it after reopening the app, it doesnt work.
This is my code
private val launcherIntentGallery = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
viewModel.updateProfilePic(uri.toString())
}
private fun startGallery() {
requireActivity().intent.type = "image/*"
launcherIntentGallery.launch("image/*")
}
Is there any solution to convert the second URI to the format of first URI so i could retrieve the image when reopening the app?
Related
This question already has answers here:
Having trouble implementing ACTION_OPEN_DOCUMENT to my project
(4 answers)
Closed 6 months ago.
for the last 2 days I've been trying to implement a profile picture feature into an application using Uri and it works... for android versions < 11, and this is probably the reason, so I wanted to ask, how should I handle this problem ? Should I copy the files, store them application storage and then get the Uri? The code I'm currently using is based on this:
private var currentPhotoUri: Uri = Uri.EMPTY
private var isChanged = false
// intent launcher used for the profile picture image, on result it updates the picture
private val intentLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val photoResult: Uri? = it.data?.data
if(photoResult != null) {
// user picked from gallery
currentPhotoUri = photoResult
changeProfilePicture(photoResult) // just a function that saves the Uri with room
} else {
// user made a photo
changeProfilePicture(currentPhotoUri)
}
}
}
private fun openIntentChooserForImageSources() {
// creating gallery intent
val galleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT, MediaStore.Images.Media.INTERNAL_CONTENT_URI)
galleryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// creating camera intent
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraIntent.also { takePictureIntent ->
takePictureIntent.resolveActivity(requireActivity().packageManager)?.also {
val photoFile: File? = try {
createImageFile()
} catch (e: IOException){
null
}
photoFile?.also {
val photoFileUri: Uri = FileProvider.getUriForFile(
requireContext(),
requireActivity().packageName,
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoFileUri)
}
}
}
val intentChooser = Intent.createChooser(galleryIntent, "Select an app")
intentChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(cameraIntent))
intentLauncher.launch(intentChooser)
}
#Throws(IOException::class)
private fun createImageFile(): File {
// Create an image file name
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir = requireActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
currentPhotoUri = this.toUri()
}
}
the error I get: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord (...) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
For the case where you use ACTION_OPEN_DOCUMENT, call takePersistableUriPermission() on a ContentResolver to get durable access to the content.
I'm creating file using
private val getContent = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
uri?.let { writeToFile(requireActivity(), it) }
}
and then launch it:
getContent.launch("file.csv")
so when i'm in launcher i can rename file before saving and also change file type which is not what i expect. So can i let user change name but hardcore this type?
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
// make action
}
}
private fun createFile() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/png"
intent.putExtra(Intent.EXTRA_TITLE, "AA.png")
startForResult.launch(intent)
}
So I have some code to choose an image from phone gallery and display it in an ImageView and also use it's URI in ExifInterface and get the exif data.
But it seems that, only works for the images in internal storage and not for external storage like sdcard.
So here is what I got:
I have a button that when it's clicked, First it checkes to see if the app has READ_EXTERNAL_STORAGE permission and if not it asks for it.
After it's granted with the permission it launches the function below :
private fun launchIntentForPhotos() {
val gallery = Intent(Intent.ACTION_PICK)
gallery.type = "image/*"
startActivityForResult(Intent.createChooser(gallery, "Choose an image"), PICK_PHOTO_CODE)
}
and than for onActivityResult I have this:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == PICK_PHOTO_CODE) {
imageUri = data?.data
imageView.setImageResource(0)
imageView.setImageURI(imageUri)
showExif(imageUri)
}
}
and finally the function for EXIF data:
private fun showExif(imageUri: Uri?) {
val inputStream :InputStream
try
{
inputStream = imageUri?.let { contentResolver.openInputStream(it) }!!
val exifInterface = ExifInterface(inputStream)
// Now you can extract any Exif tag you want
// Assuming the image is a JPEG or supported raw format
val imgWidthExif: String? = exifInterface?.getAttribute(ExifInterface.TAG_IMAGE_WIDTH)
}
catch (e: IOException) {
// Handle any errors
Log.v(TAG, "ERROR")
Toast.makeText(this, "Some went wrong!", Toast.LENGTH_LONG).show()
}
The showExif doesn't work when selecting an image from external storage and also I get this error:
W/ImageView: resolveUri failed on bad bitmap uri: content://com.google.android.apps.photos.contentprovider/-1/1/content....
Can Someone please tell me what I'm doing wrong?!
Ok I found a solution and it works fine for me!
Instead of using ACTION_PICK I used ACTION_GET_CONTENT like this:
private fun launchIntentForPhotos() {
val gallery = Intent(Intent.ACTION_GET_CONTENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
gallery.type = "image/*"
startActivityForResult(Intent.createChooser(gallery, "Choose an image"), PICK_PHOTO_CODE)
}
I have an activity from which I launch the gallery, select an image, and want to display the selected image in another activity. I have referred to the following solution and implemented the same.
How to get Image URI from Gallery?
Though I am able to pass the URI to the next activity, I cannot see anything on the image view. Any help as to where I am going wrong, appreciated.
btn_launch_gallery.setOnClickListener {
val requestCode = 0
val launchGalleryIntent = Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(launchGalleryIntent, requestCode)
}
My OnActivityResult looks like this, basically implemented the same as given in the example cited above.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode === 0 && resultCode === Activity.RESULT_OK ) {
val selectedImage: Uri? = data?.data
val picturePath = getRealPathFromURI(
selectedImage,
this
)
val intent = Intent(this, LogFoodDetail::class.java)
intent.putExtra("image_from_gallery", picturePath)
try {
startActivity(intent)
}
catch (e: Exception)
{
e.printStackTrace()
Log.e("Error",e.printStackTrace().toString())
}
}
}
fun getRealPathFromURI(contentURI: Uri?, context: Activity): String? {
val projection =
arrayOf(MediaStore.Images.Media.DATA)
val cursor = context.managedQuery(
contentURI, projection, null,
null, null
)
?: return null
val column_index = cursor
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
return if (cursor.moveToFirst()) {
// cursor.close();
cursor.getString(column_index)
} else null
// cursor.close();
}
In my next activity, I getting the intent like this and passing the URI to ImageView. However, I cannot see the image. I get the following error - W/System.err: java.io.FileNotFoundException: No content provider: /storage/emulated/0/DCIM/Camera/***.jpg
val resId = intent.getStringExtra("image_from_gallery")
val imgThumbnail: ImageView = findViewById(R.id.food_thumbnail)
try{
val imageStream: InputStream? = contentResolver.openInputStream(Uri.parse(resId))
val bitmap = BitmapFactory.decodeStream(imageStream)
imgThumbnail.setImageBitmap(bitmap)
}
catch (e: Exception)
{
e.printStackTrace()
}
I see the following image in the next activity:
UPDATE:
As commented by #blackapps in his answer passing the URI as a string to the next activity on an intent.putExtra() and resolving the URI in the subsequent activity solved it, the updated code in OnActivityResult() is,
...
val selectedImage: Uri? = data?.data
val intent = Intent(this, LogFoodDetail::class.java)
intent.putExtra("image_from_gallery",
selectedImage.toString())
startActivity(intent)
Dont convert a nice uri to a file system path.
Uri uri = data.getData();
Pass the obtained uri directly to the next activity.
And there you can use it for
imageView.setImageUri(uri);
Instead of the uri you can also pass the uri as string with uri.toString().
You can directly load an local image Uri using:
imgThumbnail.setImageUri(yourUri);
Instead of sending the string path to the activity, you should send the raw uri and then set it directly to the imageView.
Initially I choose a Intent chooser to pick up a media file, and in onActivityResult i got the uri
fun openVideo(view: View) {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
intent.type = "video/*"
startActivityForResult(
Intent.createChooser(intent, "Select video"),
REQUEST_TAKE_GALLERY_VIDEO
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
...
...
val selectedVideoUri: Uri? = data!!.data
val vidPath: String = selectedVideoUri.toString()
video_uri = Uri.parse(vidPath)
// the video uri looks like below
//content://com.mi.android.globalFileexplorer.myprovider/external_files/Download/Dolittle%20(2020)%20%5B720p%5D%20%5BBluRay%5D%20%5BYTS.MX%5D/Dolittle.2020.720p.BluRay.x264.AAC-%5BYTS.MX%5D.mp4
initializePlayer(video_uri)
...
...
}
}
Then I was able to playing the local file in exoplayer.
fun initializePlayer(video_uri) {
println("log initializePlayer called")
player = ExoPlayerFactory.newSimpleInstance(this) // `player` was defined in GlobalVariables
id_player_view.player = player
if (video_uri != null) {
println("log $video_uri ${video_uri!!::class.simpleName}")
id_player_view.useController = true
var mediaSource: MediaSource = buildMediaSource(video_uri!!)
player?.playWhenReady = playWhenReady!!
player?.seekTo(currentWindow!!, playBackPosition!!)
player?.prepare(mediaSource, false, false)
}
}
But now, think that I saved the uri in database as a string.
Then, assume I retrieved the content from database and pass it as a intent to video playing activity
now, if i write a code like this, and call initializePlayer the video is not playing.
video_uri = Uri.parse((intent.getStringExtra("video_url")))
println("log $video_uri ${video_uri!!::class.simpleName}")
initializePlayer(video_uri)
i.e for all hardcoded content uri of local media files. the exoplayer is not working.
How to solve this issue ?
think that I saved the uri in database as a string
You no longer have rights to access the content.
How to solve this issue ?
Use ACTION_OPEN_DOCUMENT to let the user choose the content. Then, in onActivityResult(), call takePersistableUriPermission() on a ContentResolver, to have access to that content for a longer period of time. See this blog post and the documentation for more.