How to save image to camera folder in Android Q?
Solution 1
The most generalized version I was able to write is:
private Uri saveImage(Context context, Bitmap bitmap, @NonNull String folderName, @NonNull String fileName) throws IOException {
OutputStream fos = null;
File imageFile = null;
Uri imageUri = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + folderName);
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
if (imageUri == null)
throw new IOException("Failed to create new MediaStore record.");
fos = resolver.openOutputStream(imageUri);
} else {
File imagesDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).toString() + File.separator + folderName);
if (!imagesDir.exists())
imagesDir.mkdir();
imageFile = new File(imagesDir, fileName + ".png");
fos = new FileOutputStream(imageFile);
}
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos))
throw new IOException("Failed to save bitmap.");
fos.flush();
} finally {
if (fos != null)
fos.close();
}
if (imageFile != null) {//pre Q
MediaScannerConnection.scanFile(context, new String[]{imageFile.toString()}, null, null);
imageUri = Uri.fromFile(imageFile);
}
return imageUri;
}
If you've found a better way, post here, I'll mark it as answer.
Solution 2
Can't we use media store for above and below Android Q? I have tried following way and it is working fine.
private fun writeImage() {
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val imageDetail = ContentValues().apply {
put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "${System.currentTimeMillis()}.jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
val contentUri = contentResolver.insert(uri, imageDetail)
contentUri?.let {
contentResolver.openFileDescriptor(contentUri, "w", null).use { pd ->
pd?.let {
/* val fos = FileOutputStream(it.fileDescriptor)
val array = getBitmapToBase64()
fos.write(array, 0, array.size)
fos.close()
*/
// or
// Your logic to write an Image file.
}
}
imageDetail.clear()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
imageDetail.put(MediaStore.Images.Media.IS_PENDING, 0)
contentUri.let { contentResolver.update(it, imageDetail, null, null) }
}
// open the saved image file with gallery app
Snackbar.make(
findViewById(android.R.id.content), "saved", Snackbar.LENGTH_LONG
).setAction("Show") {
val intent = Intent(Intent.ACTION_VIEW, contentUri)
startActivity(intent)
}.show()
} ?: Toast.makeText(this, "not saved", Toast.LENGTH_LONG).show()
}
Solution 3
Using this document : https://developer.android.com/training/data-storage
Create temp file first
val mTempFileRandom = Random()
fun createTempFile(ext:String, context:Context):String {
val path = File(context.getExternalCacheDir(), "AppFolderName")
if (!path.exists() && !path.mkdirs())
{
path = context.getExternalCacheDir()
}
val result:File
do
{
val value = Math.abs(mTempFileRandom.nextInt())
result = File(path, "AppFolderName-" + value + "-" + ext)
}
while (result.exists())
return result.getAbsolutePath()
}
Send file from path
copyFileToDownloads(this@CameraNewActivity, File(savedUri.path))
Copy data to storage
MAIN_DIR: Main folder name in which you want to store image (Like App Name) IMAGE_DIR: Sub folder if you want to create.
fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {
// Create an image file name
val timeStamp = SimpleDateFormat(DATE_FORMAT_SAVE_IMAGE).format(Date())
val imageFileName = "JPEG_$timeStamp.jpg"
val resolver = context.contentResolver
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, imageFileName)
put(MediaStore.Images.Media.MIME_TYPE, IMAGE_MIME_TYPE)
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + MAIN_DIR + File.separator + IMAGE_DIR + File.separator)
}
resolver.run {
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
uri
}
} else {
val authority = "${context.packageName}.provider"
val imagePath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)?.absolutePath
val destinyFile = File(imagePath, imageFileName)
val uri = FileProvider.getUriForFile(context, authority, destinyFile)
FileUtils.scanFile(context, destinyFile.absolutePath)
uri
}?.also { uri ->
var writtenValue = 0L
// Opening an outputstream with the Uri that we got
resolver.openOutputStream(uri)?.use { outputStream ->
downloadedFile.inputStream().use { inputStream ->
writtenValue = inputStream.copyTo(outputStream)
Log.d("Copy Written flag", " = $writtenValue")
}
}
}
}
ScanFile : ( More detail : https://developer.android.com/reference/android/media/MediaScannerConnection )
fun scanFile(context:Context, path:String) {
MediaScannerConnection.scanFile(context,
arrayOf<String>(path), null,
{ newPath, uri-> if (BuildConfig.DEBUG)
Log.e("TAG", "Finished scanning " + newPath) })
}
VolodymyrH
Hey! My name is Volodymyr, I'm Software Engineer who likes Android :)
Updated on July 08, 2022Comments
-
VolodymyrH almost 2 years
I need to save an image to camera folder, but as Android Q getExternalStoragePublicDirectory is deprecated, I do it in another way. What I have (this method receive bitmap and its name):
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ContentResolver resolver = mContext.getContentResolver(); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + IMAGES_FOLDER_NAME); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); OutputStream fos = resolver.openOutputStream(imageUri); saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); fos.close(); } else { String imagesDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DCIM).toString() + File.separator + IMAGES_FOLDER_NAME; File file = new File(imagesDir); if (!file.exists()) { file.mkdir(); } File image = new File( imagesDir, name + ".png" ); final long fileHashCode = image.hashCode(); Logger.d(TAG, "saveImage, saving image file, hashCode = " + fileHashCode); FileOutputStream fos = new FileOutputStream(image); saved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); fos.close(); }
That perfectly works for all needed OS versions, but it looks inaccurate and I'd like to find a more common way. I tried to play around with content values or try some similar way as for Q, but it doesn't work. I've seen many questions here, but neither of them can help me.
The question is how can I optimize saving for OS lower than Q?
-
Sagar gujarati over 4 yearsHow can i display this image using Environment.getExternalStorageDirectory() +"DCIM/" + IMAGES_FOLDER_NAME bcz it's not working in android 10
-
Vikash Parajuli about 4 yearsCan you please solve this stackoverflow.com/questions/61984396/save-txt-file-on-android-q
-
RRGT19 almost 4 yearsWhat is the
IMAGES_FOLDER_NAME
for? needs to be a custom folder name? -
VolodymyrH almost 4 years@RRGT19, optional folder name.
-
gpl about 3 yearsbest solution I have seen