In an Android app the user can record his voice. The sound recording is saved in a file defined by:
audioTempFile = File(getFilesDir(), "Audio_Temp_File")
Then I want to give the user the choice of the final folder and file name for the file to be saved.
So the audioTempFile above can be used for a possible next recording without destroying the current one.
For example let us assume I want the file to be saved in this file called finalFile :
val rootDir = getFilesDir()
val storeDir = File(rootDir, "MyStorageDirectory")
val finalFile = File(storeDir, "MyFinalFileName")
How can I move or possibly copy audioTempFile to finalFile?
I did not find any clear answer by searching the net.
I don't know about saving to a specific location, but this is the way I'm saving the recordings in my app.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
try{
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, allFiles[position].name)
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MUSIC)
val savedAudio = context.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
val outputStream = savedAudio?.let {
context.contentResolver.openOutputStream(it)
}
val fis = FileInputStream(allFiles[position])
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0) {
outputStream?.write(buffer, 0,length)
}
Toast.makeText(context, "Audio Saved to Music Folder", Toast.LENGTH_SHORT).show()
}catch (e : IOException){
Toast.makeText(
context,
"There was an error saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}
}else{
try {
val audioDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
val audio = File(audioDir, allFiles[position].name)
val fis = FileInputStream(allFiles[position])
val fos = FileOutputStream(audio)
var length : Int
val buffer = ByteArray(8192)
while (fis.read(buffer).also { length = it } > 0){
fos.write(buffer,0,length)
}
Toast.makeText(context, "Audio Saved to Music Folder", Toast.LENGTH_SHORT).show()
}catch (e : IOException){
Toast.makeText(
context,
"There was an error saving the file",
Toast.LENGTH_SHORT
).show()
e.printStackTrace()
}
}
You would need MANAGE_EXTERNAL_STORAGE permission if u want to save files other than public directories. It's recommended to use MediaStore API to save your files.
Do correct me if I'm wrong as I'm still learning.
Hope this helps u :)
Related
I have a page in my application where the user select an image from the gallery and save it as part of needed details. The image will be sent to a server through API request using Ktor. I have tried to convert the image bitmap to file but I get "operation not permitted" exception, and tired to look for a solution but nothing worked. One of the solutions recommended to add use permission for storage in the Manifest:
It didn't work as well.
The code is too long but here the block where I need to convert and save the file:
fun bitmapToFile(bitmap: Bitmap, fileNameToSave: String): File? { // File name like "image.png"
//create a file to write bitmap data
var file: File? = null
return try {
file = File(Environment.DIRECTORY_PICTURES + File.separator + fileNameToSave)
file.createNewFile()
//Convert bitmap to byte array
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos) // YOU can also save it in JPEG
val bitmapData = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(file)
fos.write(bitmapData)
fos.flush()
fos.close()
file
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
e.printStackTrace()
file // it will return null
}
}
fun handleChangeImage() {
try{
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, it)
} else {
val source = ImageDecoder.createSource(context.contentResolver, it)
bitmap = ImageDecoder.decodeBitmap(source)
}
}
//fixme: file conversion throw operation denied error
val fileName = imageUri?.lastPathSegment.toString().replace(":", ".")
val fileInput = if (bitmap != null)
bitmapToFile(bitmap!!, fileName)
else null
if(fileInput == null)
throw Exception("Error")
assetImageInput.value = fileInput
} catch (e: FileNotFoundException) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
}
}
After converting the image it should be saved as "assetImageInput.value" and be sent through the API as a parameter.
I am currently working with one of my app updates and I am looking for a way to save some files at the root of shared internal storage.
Don't confuse with the word shared here. I just meant here with the phone's internal/external storage which holds a large amount of data.
Now coming to the main point, I have an app that uses the FFmpeg library for android and it records the live streams and saves it into the phone's storage.
Now here's the problem I don't want to save this file in my app package folder.
e.g: /storage/emulated/0/Android/data/com.app.package/files/...
I want to save this video file in the root of the storage folder where I can create a special folder for my app just like WhatsApp then save all required data in it only.
e.g: /storage/emulated/0/MyApp/...
To be more concise I want my app just like WhatsApp which has a separate folder in internal storage for storing its app-related data.
Now, so far I tried these.
getExternalFilesDir(null)?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/files
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/files/Download
externalCacheDir?.absolutePath -> /storage/emulated/0/Android/data/com.app.package/cache
filesDir?.absolutePath -> /data/user/0/com.app.package/files
Environment.getRootDirectory().absolutePath -> /system
Environment.getExternalStorageDirectory().absolutePath -> /storage/emulated/0
Environment.getExternalStoragePublicDirectory("").absolutePath -> /storage/emulated/0
Now these to methods can work for me but they are deprecated getExternalStorageDirectory() & getExternalStoragePublicDirectory()
Just keep in mind my solution is related to the WhatsApp storage folder.
I searched a lot but not so much help as I don't want to go with just a copy-paste and hack solutions. I want to do it in a clean way.
I am trying to open a File Picker via intent for this I have set permission
....
....
and for intent.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "*/*"
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
startActivityForResult(Intent.createChooser(intent, "Select .... file"), RC)
Now here on Android 10, a file picker is not opening and this works perfectly if I add the requestLegacyExternalStorage flag in the manifest.
Or
if I set android:maxSdkVersion="29" also worked but here google wants to use Some SAF or Media Store API but I don't get it I just want to pick a file just a simple txt nothing else.
You can save video file to external storage using this:
#RequiresApi(api = Build.VERSION_CODES.Q)
#Throws(FileNotFoundException::class)
fun addToApiAbove29Gallery(
context: Context,
file: File?,
fileName: String?,
destinationPath: String?,
action: () -> Unit
) {
val valuesvideos: ContentValues
valuesvideos = ContentValues()
valuesvideos.put(
MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM
+ File.separator + context.resources.getString(R.string.app_name)
+ File.separator + File(destinationPath).name
)
valuesvideos.put(MediaStore.Video.Media.TITLE, "${fileName}.mp4")
valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "${fileName}.mp4")
valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1)
val resolver = context.contentResolver
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uriSavedVideo = resolver.insert(collection, valuesvideos)
val pfd = context.contentResolver.openFileDescriptor(uriSavedVideo!!, "w")
val executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
val handler = Handler(Looper.getMainLooper())
executor.execute {
if (pfd != null) {
try {
val out =
FileOutputStream(pfd.fileDescriptor)
val inputStream = FileInputStream(file)
val buf = ByteArray(8192)
var len: Int
while (inputStream.read(buf).also { len = it } > 0) {
out.write(buf, 0, len)
}
out.close()
inputStream.close()
pfd.close()
valuesvideos.clear()
valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 0)
try {
setExifFromUri(
context,
uriSavedVideo,
File(destinationPath)
)
} catch (e: IOException) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
}
handler.post {
context.contentResolver.update(uriSavedVideo, valuesvideos, null, null)
action()
}
}
}
}
Note that this code is only for android 10 and above, because you can easily save the video file to ecternal storage below android 10.
Context:
My project is in the space of WiFi P2P/ WiFi Direct. I have found and followed some useful tutorials and questions and have a working app for sending Images between phones.
A socket is opened and an OutputStream sent to the other device. Device 2 receives the stream and has to assume the mime Type in order to compress and save to a file.*
EDIT: *Only has to compress for images, but needs to know the File type to save it to storage.
I would now like to open up this feature to transfer a file of any (common) type and have the other device happily receive it and stash it in the appropriate location (Or just all to Downloads might make it simpler).
My question:
What can I use to determine the MIME type for all (image, text, calendar events, contacts, videos) files coming in as an InputStream.
Current code attempts:
I have tried using BitmapFactory.decodeStream() to receive options and mimeType. This only works for Images.
guessContentTypeFromStream() hasn't worked at all and I think it might only be suitable for streaming from internet source?
Any clarifications/tips on how Storage access framework should be used here, or techniques to determine mimeType are appreciated!
Below is code used on the Receive side:
inner class RxClass: Thread() {
lateinit var socket: Socket
lateinit var serverSocket: ServerSocket
override fun run() {
try {
serverSocket = ServerSocket(8888)
socket = serverSocket.accept()
//If this line is reached, then a connection has been established between server and client
Log.d("RECEIVE", "Connection established")
lateinit var bitmap: Bitmap
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
var mimeType: String? = null
lateinit var path: String
try {
inputStream = socket.getInputStream()
Log.d("RECEIVE", "got InputStream")
runOnUiThread { Toast.makeText(applicationContext, "Receiving file...", Toast.LENGTH_SHORT).show() }
//mimeType = guessContentTypeFromStream(inputStream)
val options = BitmapFactory.Options()
bitmap = BitmapFactory.decodeStream(inputStream, null, options)!! //TODO: this needs to be for all file types
mimeType = options.outMimeType
Log.d("RECEIVE", "Decoded input, Type: $mimeType")
} catch (e: IOException) {
e.printStackTrace()
}
when(mimeType){ //I was hoping that my attempt would let me know what MIME type was and then here I can act on it differently if neccessary
"image/jpeg" -> path = Environment.DIRECTORY_PICTURES
"image/jpg" -> path = Environment.DIRECTORY_PICTURES
"image/bmp" -> path = Environment.DIRECTORY_PICTURES
"image/gif" -> path = Environment.DIRECTORY_PICTURES
"image/png" -> path = Environment.DIRECTORY_PICTURES
"audio/mpeg" -> path = Environment.DIRECTORY_MUSIC
"audio/x-wav" -> path = Environment.DIRECTORY_MUSIC
"video/wav" -> path = Environment.DIRECTORY_MOVIES
"video/mp4" -> path = Environment.DIRECTORY_MOVIES
"text/plain" -> path = Environment.DIRECTORY_DOCUMENTS
"text/html" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcard" -> path = Environment.DIRECTORY_DOCUMENTS
"text/x-vcalendar" -> path = Environment.DIRECTORY_DOCUMENTS
null -> Log.d("RECEIVE", "MIMETYPE is NULL")
}
Log.d("RECEIVE", "Save file in location: $path")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = contentResolver
val contentValues = ContentValues()
//contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, name)
//contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
//contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, path)
val rxUri: Uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
val inputStream = socket.getInputStream()
outputStream = resolver.openOutputStream(rxUri)!!
Log.d("RECEIVE", "Sending File: $rxUri")
inputStream.copyTo(outputStream)
if(bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
runOnUiThread { Toast.makeText(applicationContext, "File Saved", Toast.LENGTH_SHORT).show() }
}
Log.d("RECEIVE", "close streams")
outputStream.flush()
outputStream.close()
inputStream.close()
runOnUiThread {
ReceivedImage.setImageURI(rxUri)
}
} else { //Version less than Android Q is not implemented
}
} catch (e: IOException){
e.printStackTrace()
}
}
}
Useful links
https://developer.android.com/training/data-storage
https://developer.android.com/training/data-storage/shared/media#add-item
https://developer.android.com/reference/kotlin/android/graphics/BitmapFactory
Thanks in advance
I tried saving video to gallery like i found on example:
private fun saveVideoToInternalStorage3(filePath: String) {
val newfile: File
try {
val currentFile = File(filePath)
val fileName = currentFile.name
val cw = ContextWrapper(applicationContext)
val directory = cw.getDir("videoDir", Context.MODE_PRIVATE)
newfile = File(directory.absolutePath, fileName)
if(currentFile.exists()) {
val `in`: InputStream = FileInputStream(currentFile)
val out: OutputStream = FileOutputStream(newfile)
// Copy the bits from instream to outstream
val buf = ByteArray(1024)
var len: Int
while(`in`.read(buf).also {len = it} > 0) {
out.write(buf, 0, len)
}
scanner(filePath)
`in`.close()
out.close()
Log.d("", "Video file saved successfully.")
Toast.makeText(this, "Push Video Saved TO Gallery", Toast.LENGTH_SHORT).show()
} else {
Log.d("", "Video saving failed. Source file missing.")
}
} catch(e: java.lang.Exception) {
Log.d("", "EXS " + e.message)
e.printStackTrace()
}
And it works fine it shows message "Video file saved successfully." But there is no video in my gallery.
After searching for problem i found post that says you need a scanner and i tried:
private fun scanner(path: String) {
MediaScannerConnection.scanFile(this, arrayOf(path), null, object: OnScanCompletedListener {
override fun onScanCompleted(path: String, uri: Uri?) {
Log.i("", "Finished scanning $path")
}
})
But still no luck. Does any1 know what is going on? Saving images in gallery works fine btw.
I am trying to save images to an album with the app name inside the gallery, so far I managed to save the image into the gallery but the problem is that the album name is always "Pictures", I've checked all of the other posts out there and nothing worked for me...
here is my code
val fileName = "abc"
val ImageToSave /*the image that I save, I send it value through method*/
val imageDir = File(activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES),"appName")
val image = File(imageDir,fileName)
if (!imageDir.exists())
imageDir.mkdirs()
val contentValues = ContentValues()
contentValues.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
contentValues.put(MediaStore.Images.Media.DATA, image.absolutePath)
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
contentValues.put(MediaStore.Images.Media.BUCKET_DISPLAY_NAME, "appName")
val url = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
val out = contentResolver.openOutputStream(url)
imageFromView.compress(Bitmap.CompressFormat.JPEG, 100, out)
Toast.makeText(activity, "saved", Toast.LENGTH_SHORT).show()
I am saving the image without any problem I just want to change the album name. thank you in advance...
UPDATE
I tried to just create the image file without the contentValue but it appears that something is wrong with the file I keep getting an error that says "File does not exist" here is my code now
val imageDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "appName")
if (!imageDir.exists())
imageDir.mkdirs()
val image = File(imageDir, fileName)
if (!image.exists()) {
val out = FileOutputStream(image)
ImageToSave.compress(Bitmap.CompressFormat.JPEG, 100, out)
Toast.makeText(activity, "saved", Toast.LENGTH_SHORT).show()
} else
Toast.makeText(activity, "Already saved", Toast.LENGTH_SHORT).show()
also, I made sure that I have the ask permission in my manifest file, and I am asking for permission when the user presses on the button... I made sure that my application has permission to read and write to files....
After a lot of research, I was able to find a solution for my problem, all I had to do is add
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/AppName/")
this will create a folder with your app name inside the pictures folder, in the gallery, it will appear with the app name you entered... for extra detail read this link
I found out that a huge chunk of the code that I wrote was not needed,I ended up removing all the extra stuff, this is the end result
UPDATE
I've updated the code so it works with the old versions of android as well
fun saveImage(itemImage: View, context: Context) {
val imageName: String
val imageToSave = getBitmapFromView(itemImage)
val exists: Boolean
ByteArrayOutputStream().apply {
imageToSave.compress(Bitmap.CompressFormat.JPEG, 100, this)
imageName = "ChatOut_" + UUID.nameUUIDFromBytes(this.toByteArray()).toString().replace("-", "") + ".jpg"
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,arrayOf(MediaStore.Images.Media.DISPLAY_NAME), "${MediaStore.Images.Media.DISPLAY_NAME} = '$imageName' ", null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC").let {
exists = it?.count ?: 0 >= 1
it?.close()
}
if (!exists) {
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.DISPLAY_NAME, imageName)
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/ChatOut/")
}
val url = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
val out = context.contentResolver.openOutputStream(url)
imageToSave.compress(Bitmap.CompressFormat.JPEG, 100, out)
Toast.makeText(context, "saved", Toast.LENGTH_SHORT).show()
} else
Toast.makeText(context, "Already saved", Toast.LENGTH_SHORT).show()
}else{
val imageDir = File("${Environment.getExternalStorageDirectory()}/ChatOut/")
if (!imageDir.exists())
imageDir.mkdirs()
val image = File(imageDir,imageName)
if (!image.exists()){
val outputStream = FileOutputStream(image)
imageToSave.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
outputStream.close()
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.DISPLAY_NAME, imageName)
put(MediaStore.Images.Media.DATA, image.absolutePath)
}
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
Toast.makeText(context, "saved", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(context, "Already saved", Toast.LENGTH_SHORT).show()
}
}
}
fun getBitmapFromView(view: View): Bitmap {
return Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888).apply {
Canvas(this).apply {
view.draw(this)
}
}
}
I hope this helps others :) have a good time!
It is because you are using Environment.DIRECTORY_PICTURES
You should create a custom directory instead, here is an example:
String path = Environment.getExternalStorageDirectory().toString();
File appDirectory = new File(path + "/" + "FolderName");
appDirectory.mkdirs();