Migrating to Scoped Storage for Android Q - android

When a user takes a photo in my app the image gets saved locally in the internal storage. I also allow then to be moved to the external storage where they can grab then by plugging the device into a computer and getting them off of there if they want.
I would do that by doing this
val tempDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "My_App_Folder")
val baseFile = File(filePath)
if(baseFile.exists()){
val targetFile = File(directory.path+File.separator+targetFileName)
baseFile.copyTo(targetFile)
baseFile.delete()
}
Image would end up in Pictures/My_App_Folder like I want
Now trying to move to MediaStore to support the scoped storage I am not sure how to save the file to the same folder.
I tried doing
val values:ContentValues = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE,"image/jpg")
values.put(MediaStore.Images.Media.DATE_ADDED,System.currentTimeMillis() / 1000)
values.put(MediaStore.Images.Media.TITLE, targetFileName)
values.put(MediaStore.Images.Media.DISPLAY_NAME, targetFileName)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
values.put(MediaStore.Images.Media.DATE_TAKEN,System.currentTimeMillis())
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES+"/My_App_Folder")
uri = contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL),values)
}else{
uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
}
But nothing appears in that folder now, I feel like I am missing something fundamental with MediaStore and I am not sure what it is

As Mike pointed out I have to save the image using the output stream from the uri after inserting into the content resolver so a working backwards compatible example looks like this.
private fun saveFileToExternalStorage(directory:File?,filePath:String,targetFileName:String){
var uri:Uri? = null
val values:ContentValues = ContentValues()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
values.put(MediaStore.Images.Media.MIME_TYPE,"image/jpg")
values.put(MediaStore.Images.Media.DATE_ADDED,System.currentTimeMillis() / 1000)
values.put(MediaStore.Images.Media.TITLE, targetFileName)
values.put(MediaStore.Images.Media.DISPLAY_NAME, targetFileName)
values.put(MediaStore.Images.Media.DATE_TAKEN,System.currentTimeMillis())
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES+"/My_App_Folder")
values.put(MediaStore.Images.Media.IS_PENDING, 1)
uri= contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),values)
}
val baseFile = File(filePath)
if(baseFile.exists()){
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){
directory?.let {
val targetFile = File(it.path+File.separator+targetFileName)
baseFile.copyTo(targetFile)
}
}else{
uri?.let {
val outputStream: OutputStream? = contentResolver.openOutputStream(uri)
outputStream?.let{
val inputStream: InputStream = File(filePath).inputStream()
inputStream.copyTo(outputStream,1024)
}
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(uri,values,null,null)
}
}
baseFile.delete()
}
}

Related

Can not get file on android API 29 up

I can not read a file that I'm saving. I have declared and asking for storage permissions.
fun saveImage(bitmap: Bitmap, context: Context): String {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = contentValues()
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + context.getString(R.string.app_name))
values.put(MediaStore.Images.Media.IS_PENDING, true)
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values).also { uri ->
if (uri != null) {
if (uri != null) {
saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
}
//val split = file.path.split(":".toRegex()).toTypedArray() //split the path.
Log.d(
"Tag",
"v--- uri.path - ${uri.path}, "
)
return uri.path.toString() //assign it to a string(your choice).
}
}
} else { // below API 29 }
}
then in onCreate or any other method I want to access that file and show on preview.
val previousPath = """/external/images/media/356""" //preferenceManager.getString(...)
//val file = File(path)
//previewImage.setImageURI(Uri.parse(previousPath))
val file = FileUtils().getFile(applicationContext, Uri.parse(previousPath))
Log.d("Tag", "Yo --- "+file?.path + " , "+ file?.name)
// val myBitmap = BitmapFactory.decodeFile()
// previewImage.setImageBitmap()
But this is always empty. With this getFile method, I get null pointer exception. I need the file to upload to server. How can I get this file?
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
You used that obtained uri to save your file content to storage with:
context.contentResolver.openOutputStream(uri)
Now if you wanna read that file use the same uri again. Just use:
context.contentResolver.openInputStream(uri)
Use the same uri!
Dont mess around with the File class.

How to save a bitmap to external storage(kotlin)?

I am making a project that generates qr code. When user generates a qr code, A download button appears on the screen. If the user clicks this button. Application should save the image(qr code) to external storage. I only want to create a function to save the qr code. But i could not find any useful source. How can i save a bitmap to external storage?
use this function and pass bitmap on button click
from fragment
saveBitmapInStorage(bitmap!!, requireContext())
from activity
saveBitmapInStorage(bitmap!!, this)
main function
open fun saveBitmapInStorage(bitmap: Bitmap, context: Context) {
val filename = "QR_"+"${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.contentResolver?.also { resolver ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
} else {
val imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
}
}
why I share because I'm using this code in my current project and its working for me
hope helpful for you.

How to use ContentResolver while merging Video and Audio file using isoparser library in Android 10

I am trying to merge a video file (.mp4 format) and an audio file (.aac format) using "com.googlecode.mp4parser:isoparser". The merged file is getting saved properly if I use the deprecated method "Environment.getExternalStorageDirectory()" to create FileOutputStream.
But I don't want to use this deprecated method. So I am using ContentResolver to create FileOutputStream, but then merged file is getting saved without audio. I want the merged file to be saved in external storage under Movies folder so that it will be visible in Gallery app.
Below is my code.
val mp4File = MovieCreator.build(FileDataSourceImpl(videoFilePath))
val aacTrack = AACTrackImpl(FileDataSourceImpl(audioFilePath)
val nuTracks : ArrayList<Track> = arrayListOf()
for (track in mp4File.tracks){
if (track.handler.equals("vide")){
nuTracks.add(track)
}
}
nuTracks.add(aacTrack)
val movie = Movie()
movie.tracks = nuTracks
val mp4file = DefaultMp4Builder().build(movie)
val resolver = context!!.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "FinalMergedFile")
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Movies/${myFolder}/")
}
val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val uriSavedVideo = resolver.insert(collection, contentValues)
val pfd = resolver.openFileDescriptor(uriSavedVideo!!, "rwt")
val fc = FileOutputStream(pfd!!.fileDescriptor).channel
mp4file.writeContainer(fc)
fc.close()

How can I read File from MediaStore in Relative Path?

In Android 10 or higher, I used MediaStore to save files in Downloads, shared storage.
The code I used to save the file is as follows:
GlobalScope.launch {
val values = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, "file.txt")
put(MediaStore.Downloads.MIME_TYPE, "text/plain")
put(MediaStore.Downloads.IS_PENDING, 1)
put(MediaStore.Downloads.RELATIVE_PATH,Environment.DIRECTORY_DOWNLOADS+ File.separator+"MyApp/SubDir")
}
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val item = contentResolver.insert(collection, values)!!
contentResolver.openFileDescriptor(item, "w", null).use {
FileOutputStream(it!!.fileDescriptor).use { outputStream ->
outputStream.write("test file".toByteArray())
outputStream.close()
}
}
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(item, values, null, null)
Now my file is save in Downloads/myApp/subDir/file.txt
If so, how can I read this file if I don't know the Uri of this file but know the RELATIVE_PATH used to save it?
It is now possible with the helps of SimpleStorage:
val fileList = MediaStoreCompat.fromRelativePath(this, Environment.DIRECTORY_DOWNLOADS)
val singleFile = MediaStoreCompat.fromRelativePath(this, Environment.DIRECTORY_DOWNLOADS + "/MyApp/SubDir/", "file.txt")

Store Image via Android Media Store in new Folder

I am using the following to store images created in my app in the gallery of Android:
MediaStore.Images.Media.insertImage(contentResolver, bitmap, "SomeTitle", "Description");
This will store the images in the Picture-Device-Folder and add them to the Gallery.
I now want to create a specific image folder for my app, so that images are stored in the folder "MyApp" instead of "Picture". How can I do that?
I found the solution hidden here: https://stackoverflow.com/a/57265702/289782
I will quote it here since the original question is rather old and the great answer by User Bao Lei is ranked rather low.
There were several different ways to do it before API 29 (Android Q) but all of them involved one or a few APIs that are deprecated with Q. In 2019, here's a way to do it that is both backward and forward compatible:
(And since it is 2019 so I will write in Kotlin)
/// #param folderName can be your app's name
private fun saveImage(bitmap: Bitmap, context: Context, folderName: String) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = contentValues()
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName)
values.put(MediaStore.Images.Media.IS_PENDING, true)
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri != null) {
saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
}
} else {
val directory = File(Environment.getExternalStorageDirectory().toString() + separator + folderName)
// getExternalStorageDirectory is deprecated in API 29
if (!directory.exists()) {
directory.mkdirs()
}
val fileName = System.currentTimeMillis().toString() + ".png"
val file = File(directory, fileName)
saveImageToStream(bitmap, FileOutputStream(file))
if (file.absolutePath != null) {
val values = contentValues()
values.put(MediaStore.Images.Media.DATA, file.absolutePath)
// .DATA is deprecated in API 29
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
}
}
private fun contentValues() : ContentValues {
val values = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png")
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
return values
}
private fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
EDIT: Not needed in Android 11+: (Also, before calling this, you need to have WRITE_EXTERNAL_STORAGE first.)

Categories

Resources