How to get base64 from file's URI? - android

In general, my task is to get base64 from chosen file. In order to open File Browser, I call following function:
private fun showFileBrowser() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.setType("*/*")
startActivityForResult(intent, FILE_CHOOSE_REQUEST_CODE)
}
It is successfully opened. When some file is chosen, onActivityResult is called. Here it is:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FILE_CHOOSE_REQUEST_CODE) {
// not that data.data is Uri
if(data != null && data.data != null) {
val encodedBase64 = getBase64FromPath(data.data.path)
print(encodedBase64)
}
}
}
Here is how I convert File to base64:
private fun getBase64FromPath(path: String): String {
try {
val bytes = File(path).readBytes()
return Base64.encodeToString(bytes, Base64.DEFAULT)
} catch (error: IOException) {
error.printStackTrace() // This exception always occurs
}
}
Seems like I do everything right, but I get FileNotFoundException. I don't know what is the reason for this. I didn't add any permission, because I don't to write anything to scared, I just want a user to choose a file, I will convert it to base64 and send it to the server. So, what is the problem this my code?

my task is to get base64 from chosen file
Your code has little to do with files. ACTION_GET_CONTENT is not limited to files on the device, let alone files on the filesystem that you can access.
When some file is chosen, onActivityResult is called
You get a Uri via onActivityResult(). A Uri is not a file, and getPath() on a Uri only has meaning if the scheme of that Uri is file. Most Uri values will have a scheme of content.
Replace your function with:
private fun getBase64ForUriAndPossiblyCrash(uri: Uri): String {
try {
val bytes = contentResolver.openInputStream(uri).readBytes()
return Base64.encodeToString(bytes, Base64.DEFAULT)
} catch (error: IOException) {
error.printStackTrace() // This exception always occurs
}
The AndPossiblyCrash portion of the function name is because you are going to run out of memory if the content is too large. Also note that you are doing this work on the main application thread, so your UI will be frozen while you are reading this in.

Related

Deprecated startActivityForResult and onActivityResult and File Not Uploaded Successfully

There is no error and no problem with clicking the button, but the voice file does not upload to the Firestore.
Audio selection is possible, but the message "Successfully Uploaded:" is not output, and the voice file is not uploaded to the Fire Store.
I think the cancellation line in onActivityResult and startActivityForResult is the problem.
How do I get rid of the cancellation line? And is there any other reason why the file doesn't go up on the fire store?
++I modified the Firestore rules.
MainActivity.kt
import ...
class MainActivity : AppCompatActivity() {
val AUDIO : Int = 0
lateinit var uri: Uri
lateinit var mStorage: StorageReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val musicBtn = findViewById<View>(R.id.musicBtn) as Button
mStorage = FirebaseStorage.getInstance().getReference("Uploads")
musicBtn.setOnClickListener(View.OnClickListener {
view -> val intent = Intent()
intent.setType("audio/*")
intent.setAction(Intent.ACTION_GET_CONTENT)
startActivityForResult(Intent.createChooser(intent, "Select MP3"), AUDIO)
})
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val uriTxt = findViewById<View>(R.id.uriTxt)as TextView
if (requestCode== RESULT_OK){
if (requestCode == AUDIO) {
uri = data!!.data!!
uriTxt.text = uri.toString()
upload()
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun upload() {
var mReference = mStorage.child(uri.lastPathSegment!!)
try {
mReference.putFile(uri).addOnSuccessListener {
taskSnapshot: UploadTask.TaskSnapshot? -> var url = taskSnapshot!!
val dwnTxt = findViewById<View>(R.id.dwnTxt) as TextView
dwnTxt.text = url.toString()
Toast.makeText(this, "Successfully Uploaded :)", Toast.LENGTH_LONG).show()
}
}catch (e: Exception) {
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show()
}
}
}
enter image description here
A cancellation line is created as shown in the image. How do I solve it?
startActivityResult and onActivityResult have been deprecated. You need to migrate to registerForActivityResult and ActivityResultContracts methods.
A few of the examples are given below:
https://developer.android.com/codelabs/android-app-permissions
Predefined Contracts - https://developer.android.com/reference/kotlin/androidx/activity/result/contract/ActivityResultContracts
https://developer.android.com/reference/kotlin/androidx/activity/result/contract/ActivityResultContract
https://developer.android.com/training/basics/intents/result
Another easier way you can do this is by ignoring the error and using #Deprecated("Deprecated in Java") annotation but it is recommended that you migrate.
The cancellation line means those functions are deprecated, but in this case, that is not your problem because they still behave as they used to.
This might be your issue. You have a typo here:
if (requestCode== RESULT_OK){
if (requestCode == AUDIO) {
You should be checking if the resultCode is RESULT_OK, not the requestCode. So currently your if statement will not run unless your AUDIO request code happens to be the same as Activity.RESULT_OK.
If that still doesn't solve it, you should add an OnFailureListener to find out what's happening. You can put a debug breakpoint in your OnFailureListener so you can inspect the error object for what the problem is.

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()
}
}

How to get file path and file instance from a SAF file picker request?

I am using the following function for making request and decoding URI from onActivity result . The following function works but freezes the whole screen for many seconds before generating the final file:
// Request code:
fun filePickerRequest3SAF(activity: AppCompatActivity) {
RequestIntentBuilder(IntentInit.OPEN_DOCUMENT) // Intent(Intent.ACTION_OPEN_DOCUMENT)
.addOpenableCategory()//requestIntent.addCategory(Intent.CATEGORY_OPENABLE)
.setFilteringMimeType("video/*")
.addFlagForReadPermission() //requestIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) and requestIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
.buildAndStartActivityForResult(activity)
}
//response function. here I pass received intent's URI and activity context
fun getPathFromUriOrEmpty(uri: Uri?, context: Context?): String {
if (context == null || uri == null) return ""
return getFileFromUriOrDefault(uri, context)?.path ?: ""
}
fun getFileFromUriOrDefault(uri: Uri?, context: Context?, default:File? = null): File? {
if (context == null || uri == null) return default
val resolver = context.contentResolver
val tmpFile = File(context.cacheDir, getFileNameFromUriOrNull(uri, resolver) ?: "temp_file_name.${getExtensionFromUriOrDefault(uri, context)}")
return try {
val inputStream = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(tmpFile)
outputStream.use { fileOut -> inputStream?.copyTo(fileOut) }
tmpFile
} catch (t: Throwable) {
t.printStackTrace()
default
}
}
is there a way to do it better, apart from just making a file and copying it as whole? My app is supposed to upload videos of size > 1-2 gb, so is there a way we can provide the URI / file to the upload service without actually making a copy of file? I am assuming the file upload service will also be making multiple copies to upload
Ps: I intent to support android versions KitKat to android 12/+ , so not thinking of using legacy storage or other such flags if they can be avoided with SAF as a unified solution across different android versions

ExifInterface not working with files from sdcard

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)
}

Can't open InputStream on Intent.ACTION_OPEN_DOCUMENT_TREE result

So, I wan't to make a simple recording app in which a user can select their preferred save location.
I plan on getting the Uri of the selected dir via Intent.ACTION_OPEN_DOCUMENT_TREE, save and process it with InputStream and MediaRecorder.
When I try to process the Result in my onActivityResult,
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == resources.getInteger(R.integer.request_code_preference_storage) &&
resultCode == Activity.RESULT_OK){
val uri = data!!.data
val inputstream = InputStreamHelper.readTextFromUri(uri!!, context!!)
}
I get the following error:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=65537, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/tree/primary:Download flg=0xc3 }} to activity {de.ur.mi.audidroid/de.ur.mi.audidroid.views.MainActivity}: java.lang.IllegalArgumentException: Invalid URI: content://com.android.externalstorage.documents/tree/primary%3ADownload
I don't know why it won't take the uri. DocumentFile will take it without any complaints through DocumentFile.fromTreeUri(context!!,uri!!).
The InputStreamHelper is an object I basically copied directly from the documentation.
object InputStreamHelper{
fun readTextFromUri(uri: Uri, context: Context): String {
val stringBuilder = StringBuilder()
context.contentResolver.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
var line: String? = reader.readLine()
while (line != null) {
stringBuilder.append(line)
line = reader.readLine()
}
}
}
return stringBuilder.toString()
}
}
I'm confused and confounded. It's nothing complicated but I hit a roadblock.
If you used ACTION_OPEN_DOCUMENT_TREE, that allows the user to pick a document tree, representing a collection of content. You cannot open an InputStream on that.
Perhaps you should be using ACTION_OPEN_DOCUMENT. This allows the user to pick an individual piece of content, and you can open an InputStream on that.
BTW, I think that you could replace that loop construct with:
return context.contentResolver.openInputStream(uri)?.use { it.reader().readLines() }

Categories

Resources