How to get permanent access to file URI [duplicate] - android

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.

Related

android FilrPrivider not woorking twice

The process is to create a jpg file based on the timestamp, add the uri to the list, and display it through the lisetview
the first picture is normal,but Error will be reported when the second picture is obtained
The error occurred before Log.e("addiamge","====" )
E Writing exception to parcel
java.lang.SecurityException: Permission Denial: writing
net.qingmowan.Inspection.custom.MyFileProvider uri
content://net.aaaa.bbb.fileprovider/my_image/inspection_16679577393117723686263338001557.jpg
from pid=30798, uid=10084 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:919)
at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:698)
at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:669)
at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:493)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:272)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
var photoURI: Uri? = null
val imageUris = LinkedList<Uri>();
val adapter = ImageAdapter(this, imageUris, layoutInflater)
val imageList = findViewById<ListView>(R.id.imageList)
imageList.adapter = adapter
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK && photoURI != null) {
Log.e("addiamge","====" )
imageUris.add(photoURI!!)
Log.e("images", imageUris.toString())
adapter.notifyDataSetChanged()
}
}
image.setOnClickListener {
photoURI = getPhotoUri()
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
launcher.launch(intent)
}
private fun getPhotoUri(): Uri? {
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
Log.e("获取拍照文件", "失败", ex)
null
}
photoFile?.also {
val uri = FileProvider.getUriForFile(this, "net.aaa.bbb.fileprovider", it)
return uri
}
return null
}
private fun createImageFile(): File {
val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val timeStamp = Date()
val file = File.createTempFile("inspection_${timeStamp.time}", ".jpg", storageDir).absoluteFile
Log.e("file_message", String.format("name:%s, file.absolutePath))
return file
}
Every time you get a photo from the camera, you need to create a new intent and reset the action
Move the definition of intent to the clickListener
image.setOnClickListener {
photoURI = getPhotoUri()
intent1.action = MediaStore.ACTION_IMAGE_CAPTURE
intent1.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
launcher.launch(intent1)
}
As for why I don't know, I hope someone can supplement my answer

Standardize Uri between Camera and Gallery Intent Android

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?

Video Capture intent in Android 30+ - Only owner is able to interact with pending item

I am trying to capture video on my app. It works below android API 30 but does not work on 30+. Seems like after sdk 30, android does not allow to read external storage entirely (scoped storage). I am currently having this error:
java.lang.IllegalStateException: Only owner is able to interact with pending item content://media/external_primary/video/media/57
Now I have three questions:
How can I create video capture intent that saves video to apps internal storage? (Because scoped storage limitations are for external storage)
I can get content uri at onActivityResult, how to make this uri accessible and readable? (After I read this file, I will create a temporary file with it and edit this temp file.)
What is the proper way to capture a video with scoped storage limitations?
video capture intent
private fun dispatchTakeVideoIntent() {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
takeVideoIntent.resolveActivity(packageManager)?.also {
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
}
}
}
onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK){
when(requestCode){
REQUEST_VIDEO_CAPTURE -> {
val videoUri: Uri? = data?.data
setVideo(videoUri)
}
}
}
}
videoUri looks like this: content://media/external_primary/video/media/57
setVideo function normally gets the content uri, creates a temporary file from it, compresses, and gets a thumbnail from this file. And then I upload this file to the server.
Thanks to #CommonsWare s advice, I created a file with File provider and supply uri of this file with EXTRA_OUTPUT. Now I am able to do stuff with videoUriForAddingCaptureVideo and videoPathForAddingCaptureVideo variables. I am posting this answer to give a clue to fellow developers.
private fun dispatchTakeVideoIntent() {
val videosFolder = File(
Environment
.getExternalStorageDirectory(), application.applicationContext.resources
.getString(R.string.app_name)
)
try {
if (!videosFolder.exists()) {
val isCreated: Boolean = videosFolder.mkdirs()
if (!isCreated) {
Log.e(TAG,"dispatchTakeVideoIntent : storage error")
return
}
}
} catch (e: Exception) {
e.printStackTrace()
}
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val videoFileName = "VID_" + timeStamp + "_"
val storageDir: File? = application.applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
try {
val video = File.createTempFile(
videoFileName, /* prefix */
".mp4", /* suffix */
storageDir /* directory */
)
videoUriForAddingCaptureVideo = FileProvider.getUriForFile(application.applicationContext, application.applicationContext.packageName + ".provider", video)
videoPathForAddingCaptureVideo = video.absolutePath //Store this path as globe variable
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUriForAddingCaptureVideo)
takeVideoIntent.resolveActivity(packageManager)?.also {
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
}
}
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}

Why is the image being captured returning null? [duplicate]

This question already has answers here:
Why do I get null in data parmeter of onActivityResult
(1 answer)
Android "Taking Photos Simply" tutorial does not work for me [duplicate]
(1 answer)
Closed 2 years ago.
I have this application where once the user takes a photo using the camera app, it will automatically set the ImageView in the application to that captured image. I keep getting this error and it's causing my application to crash:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=null} to activity {com.company.example/com.company.example.Camera}: java.lang.NullPointerException
The error occurs here, specifically the foodBitmap line:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val foodBitmap = Objects.requireNonNull(Objects.requireNonNull(data)!!.extras)!!["data"] as Bitmap
food_image.setImageBitmap(foodBitmap)
}
These are the two functions I'm using to capture the image. I have all the permissions set up properly:
private fun openCamera() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// Ensure that there's a camera activity to handle the intent
takePictureIntent.resolveActivity(packageManager)?.also {
// Create the File where the photo should go
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
null
}
// Continue only if the File was successfully created
photoFile?.also {
photoURI = FileProvider.getUriForFile(
Objects.requireNonNull(applicationContext),
BuildConfig.APPLICATION_ID + ".provider",
photoFile
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, 1)
}
}
}
}
#Throws(IOException::class)
private fun createImageFile(): File {
// Create an image file name
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = absolutePath
}
}
What am I doing wrong here? Why is data in onActivityResult null?

Copy video from gallery to other folder in Kotlin [duplicate]

This question already has answers here:
onActivityResult's intent.getPath() doesn't give me the correct filename
(2 answers)
Getting the Absolute File Path from Content URI for searched images
(2 answers)
Android - Get real path of a .txt file selected from the file explorer
(1 answer)
Closed 3 years ago.
I have an activity where the user can select an image/video from gallery. For images everything is working fine, however i'm struggling with videos.
This is how i call to open the gallery in case of videos:
fun getVideoFromGallery() {
if (Build.VERSION.SDK_INT < 19) {
var intent = Intent()
intent.type = "video/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(
Intent.createChooser(intent, "Select Picture")
, GALLERY_VIDEO
)
} else {
var videopicker = Intent(Intent.ACTION_OPEN_DOCUMENT);
videopicker.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
videopicker.addCategory(Intent.CATEGORY_OPENABLE)
videopicker.type = "video/*"
startActivityForResult(videopicker, GALLERY_VIDEO);
}
}
I receive the notification when the user selected the video in:
public override fun onActivityResult(requestCode:Int, resultCode:Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == GALLERY_VIDEO)
{
if (data != null)
{
val contentURI = data.data
try {
if (data.getData() != null) {
var videoURI = data.getData()
val cR = this#EnviarMultimediaActivity.getContentResolver();
val type = cR.getType(videoURI);
if(!type.isNullOrEmpty() && type.contains("video/", true)){
val videopath = videoURI.getPath()
val file = File(videopath)
Log.d(TAG, "Video uri: "+videoURI)
Log.d(TAG, "Video path: "+file.getAbsolutePath())
var videoCopy = File(Environment.getExternalStorageDirectory().absolutePath+ IMAGE_DIRECTORY + ((Calendar.getInstance().getTimeInMillis()).toString() + ".mp4"))
//file.copyTo(videoCopy, true)
copyVideoFile(file, videoCopy)
Glide
.with(this#EnviarMultimediaActivity)
.asBitmap()
.load(videoURI)
.into(object : CustomTarget<Bitmap>(){
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
val thumbnail = saveImage(resource)
val thumbnailUri = Uri.parse(thumbnail);
val videoCopyURI = Uri.parse(videoCopy.getPath())
listaFicherosEnviar.add(EnviarMultimediaFichero(null, false, 3, videoCopyURI, thumbnailUri))
adapterEnviarMultimediaImagen.swapData(listaFicherosEnviar)
}
override fun onLoadCleared(placeholder: Drawable?) {
}
})
}
}
}
catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this#EnviarMultimediaActivity, "Failed!", Toast.LENGTH_SHORT).show()
}
}
}
}
My onActivityResult is bigger but i just pasted the relevant part for videos.
If i don't try to make the copy Glide created the thumbnail and is displayed in the view.
The problem is that the copy don't work, i tried the File method copyTo and also another method implemented that receive a copy source and copy destination as parameter.
private fun copyVideoFile(sourceFile: File, destFile: File){
if (!sourceFile.exists()) {
return;
}
val source = FileInputStream(sourceFile).getChannel();
val destination = FileOutputStream(destFile).getChannel();
if (destination != null && source != null) {
destination.transferFrom(source, 0, source.size());
}
if (source != null) {
source.close();
}
if (destination != null) {
destination.close();
}
}
The problem is that sourceFile.exists() returns false, so no copy is done.
I tried to lod path and uri and is this:
Video uri: content://com.android.providers.downloads.documents/document/36
Video path: /document/36
I'm a bit lost as i don't understand why if the uri is correct (as Glide works) i can't create a File and make a copy to another File.
I requested permission and in my manifest i have:
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.CAMERA
android.permission.INTERNET
android.permission.READ_EXTERNAL_STORAGE
Later i have retrofit2 api that creates a post to the server where sends as multipart:
val requestBody: RequestBody = RequestBody.create(MediaType.parse("video/*"), file)
val multiPart: MultipartBody.Part = MultipartBody.Part.createFormData("file", file.name, requestBody)
val name = RequestBody.create(MediaType.parse("text/plain"), file.name);
This is why i need an instance of File. As said the copy is just a test, but the File instance i think is required (unless i have a different code in the retrofir2 api to add the file to the post request).
Any help is appreciated.

Categories

Resources