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.
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 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()
}
}
This question already has answers here:
Android Kotlin: Getting a FileNotFoundException with filename chosen from file picker?
(5 answers)
Create a file from a photo URI on Android
(1 answer)
Closed 2 years ago.
I want to upload a logo picked in gallery to our api via multipart.
I am using multipartFile method from the networking library witch take a File in parameter: uploading a file to server
The File object can take an Uri as constructor.
With the uri received from the gallery return, i can display the image in a ImageView, but the uri or uri.path cause FileNotFoundException when passed in FileConstructor. (internet, read, write permissions are declared in manifest)
there is all code concerning this feature:
//selected image uri
private var imageUri: Uri? = null
set(value) {
field = value
field?.let { //value after picking image is : content://com.android.providers.media.documents/document/image%3A25
uploadImageFile(imageUri= it,
route = "uploadImageRoute/",
onProgression = { progress ->
Log.d("upload", "progress= $progress")
},
onSuccess = {
Log.d("upload", "upload success !")
},
onError = { errorMessage ->
Log.d("upload", "error= $errorMessage")
})
}
}
//select image button clicked, go to image gallery to pick an image
override fun onClick(v: View?) {
val intent = Intent()
intent.type = "image/*"
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), 0)
}
//comeback from image gallery
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0 && resultCode == Activity.RESULT_OK && data != null) {
val selectedPhotoUri = data.data!!
imageUri = selectedPhotoUri
}
}
//prepare request headers
private fun configureHeaders(): MutableMap<String,Any> {
var headerMap = mutableMapOf<String, Any>()
headerMap["Content-Type"] = "multipart/form-data"
headerMap["Accept"] = "application/json, text/plain"
return headerMap
}
private fun uploadImageFile(imageUri: Uri, route: String, onProgression: (Double) -> Unit, onSuccess: () -> Unit, onError: (String?) -> Unit) {
val headers = configureHeaders()
var file = File(imageUri.path)
AndroidNetworking.upload("https://api.ourDomain.com/$route")
.addMultipartFile("file", file)
.addHeaders(headers)
.build()
.setUploadProgressListener { uploaded, total ->
val percentage = 100 * uploaded / total as Double
onProgression(percentage)
}
.getAsJSONObject(object : JSONObjectRequestListener {
override fun onResponse(response: JSONObject?) {
onSuccess()
}
override fun onError(anError: ANError?) {
onError(anError.toString())
}
})
}
How to get the real path to the file to put in parameter of File constructor ?
I send intent for recording video in this way
private fun openCamera(url: String) {
if (!viewModel.isAttachmentSaved) {
val takeVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
val file = File(url)
val videoURI = if (Build.VERSION_CODES.N <= android.os.Build.VERSION.SDK_INT) {
FileProvider.getUriForFile(
localActivity,
BuildConfig.APPLICATION_ID + ".fileprovider",
file
)
} else {
Uri.fromFile(file)
}
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoURI)
val resInfoList = requireActivity()
.packageManager
.queryIntentActivities(takeVideoIntent, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
localActivity?.grantUriPermission(
packageName, Uri.parse(url),
Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
takeVideoIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION and
Intent.FLAG_GRANT_WRITE_URI_PERMISSION and
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION and
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
startActivityForResult(takeVideoIntent, REQUEST_VIDEO)
}
}
After I that record video and play it in Mi Video, press back and confirm video file. And try to get video in onActivityResult but there are null file. Intent contains uri like this content:///external_files/..... FileProvider path is external_files
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_VIDEO && resultCode == Activity.RESULT_OK) {
Log.d("bouxView", "onActivityResult $data $requestCode")
//Toast.makeText(localActivity, R.string.text_start_compressing, Toast.LENGTH_LONG).show()
if (isViewModelInitialized()) {
val path = viewModel.videoPathLiveData.value?.run {
substringBeforeLast(FilesRepository.DELIMITER) + "copied" + FilesRepository.DELIMITER + substringAfterLast(FilesRepository.DELIMITER)
}
data?.data?.let {
try {
val fis: InputStream? = context?.contentResolver?.openInputStream(data?.data)
val videoFile = File(path)
val fos = FileOutputStream(videoFile)
val buffer = ByteArray(1024)
var length: Int = 0
var oldLength:Int = 0
while (fis?.read(buffer).also { length = (it ?: 0) } ?: 0 > 0) {
if (oldLength==0){
oldLength = length
}
fos.write(buffer, 0, length)
}
fis?.close()
fos.close()
Toast.makeText(requireContext(),"oldLength = $oldLength, path = $path, data = ${data.data.toString()}, pathLiveData = ${viewModel.videoPathLiveData.value}", Toast.LENGTH_LONG).show()
} catch (e: IOException) {
}
}
viewModel.onVideoSaved(path?:viewModel.videoPathLiveData.value)
}
}
}
If I record video and confirm it without playing all fine. What can be wrong? I also tried to find video in videoUri path that I put into MediaStore.EXTRA_OUTPUT parameter, no success. Tried to get video path from uri (How to get the Full file path from URI), no success.
It reproduced only in MIUI 11, android version 9
Looks like MIUI bug. This issue was resolved after updating MIUI.
I don't want my app to require any permissions, but I want the user to be able to select a file for reading. My app doesn't need arbitrary access to the filesystem. However, all openfiledialog implementations I have researched so far seem to assume permission to access external storage.
One workaround I can think of is to configure my app to be among the list of apps to open a certain type of file. I haven't tried this, but I hope this would work without permission to access external storage. However, user guidance would be less then ideal in this case. I would prefer a solution with a dialog and have the user pick the file.
I think this requirement does not undermine security, because the user has full control over the file my app can read. Is this possible somehow?
However, all openfiledialog implementations I have researched so far seem to assume permission to access external storage.
Set your minSdkVersion to 19, then use ACTION_OPEN_DOCUMENT, part of the Storage Access Framework.
Or, if you need your minSdKVersion to be below 19, use ACTION_GET_CONTENT on the older devices.
You will get a Uri back via onActivityResult(). Use a ContentResolver and methods like openInputStream() to consume the content identified by that Uri.
I haven't tried this, but I hope this would work without permission to access external storage
Only if you exclude file: Uri values. For example, an <intent-filter> that supports only content: Uri values would work.
Android 11 Resolve file access issue without use of MANAGE_EXTERNAL_STORAGE.
I have added code for get doc file and Upload to server.
AndroidManifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
</application>
Now, Add this lib in your project
https://github.com/FivesoftCode/FilePicker
Add below code to Activity/Fragment
FilePicker.from(activity)
.setFileTypes(FilePicker.IMAGE, FilePicker.VIDEO) //Set file types you want to pick.
.setAllowMultipleFiles(true) //Allow user to select multiple files
.setListener { files -> //Wait for results
if (files != null && files.size > 0) {
//Do something with uris.
for (items in files) {
val extension: String = getMimeType(activity!!,items)!!
if (extension == "pdf") {
val cacheDir: String = context!!.cacheDir.toString()
val getCopyFilePath = copyFileToInternalStorage(context!!,items,cacheDir)
Log.e("TAG", "getPathToUploadDoc: " + getCopyFilePath )
}
}
} else {
//Add msg here...
}
}
.setTitle("Pick a file from My Files")
.pick() //Open file picker
Add below method for get Mime Type
fun getMimeType(context: Context, uri: Uri): String? {
val extension: String?
//Check uri format to avoid null
extension = if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
//If scheme is a content
val mime = MimeTypeMap.getSingleton()
mime.getExtensionFromMimeType(context.contentResolver.getType(uri))
} else {
//If scheme is a File
//This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters.
MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(uri.path)).toString())
}
return extension
}
fun getFiledetails(uri: Uri,context: Context,getCopyFilePath:String): NormalFile? {
// var result: String? = null
if (uri.scheme == "content") {
val cursor: Cursor = context.contentResolver.query(uri,
FileLoader.FILE_PROJECTION, null, null, null)!!
try {
if (cursor != null && cursor.moveToFirst()) {
// result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
val path: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
if (path != null && path != "") {
//Create a File instance
cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID))
// cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)).toInt()
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE))
// cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
}
}
} finally {
cursor.close()
}
}
/*if (result == null) {
result = uri.path
val cut = result!!.lastIndexOf('/')
if (cut != -1) {
result = result.substring(cut + 1)
}
}*/
return file
}
fun copyFileToInternalStorage(context: Context?,uri: Uri, newDirName: String): String? {
val returnCursor = context!!.contentResolver.query(
uri, arrayOf(
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
), null, null, null
)
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
returnCursor.moveToFirst()
val name = returnCursor.getString(nameIndex)
val size = java.lang.Long.toString(returnCursor.getLong(sizeIndex))
val output: File
output = if (newDirName != "") {
val dir = File(/*context!!.filesDir.toString() + "/" +*/ newDirName)
if (!dir.exists()) {
dir.mkdir()
}
File(/*context!!.filesDir.toString() + "/" +*/ newDirName + "/" + name)
} else {
File(context!!.filesDir.toString() + "/" + name)
}
try {
val inputStream: InputStream? = context!!.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(output)
var read = 0
val bufferSize = 1024
val buffers = ByteArray(bufferSize)
while (inputStream?.read(buffers).also { read = it!! } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream?.close()
outputStream.close()
} catch (e: Exception) {
Log.e("Exception", e.message!!)
}
return output.path
}
For upload Doc
implementation 'net.gotev:uploadservice:2.1'
var uploadId = UUID.randomUUID().toString()
val url = ServerConfig.MAIN_URL
uploadReceiver.setDelegate(this)
uploadReceiver.setUploadID(uploadId)
val data = MultipartUploadRequest(mContext, uploadId, url)
.addFileToUpload(path, "attachment")
.addHeader("Authentication", getMD5EncryptedString())
.addParameter(USER_ID,1)
.setMaxRetries(5)
.startUpload()
fun getMD5EncryptedString(): String {
val encTarget = ServerConfig.AUTHENTICATE_VALUE //Any pwd
var mdEnc: MessageDigest? = null
try {
mdEnc = MessageDigest.getInstance("MD5")
} catch (e: NoSuchAlgorithmException) {
println("Exception while encrypting to md5")
e.printStackTrace()
}
mdEnc!!.update(encTarget.toByteArray(), 0, encTarget.length)
var md5 = BigInteger(1, mdEnc.digest()).toString(16)
while (md5.length < 32) {
md5 = "0$md5"
}
return md5
}