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?
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.
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) })
}
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()
}
Related
Good Morning,
I have a problem with MediaStore on Android 11. I have to download .zip file, then open it and get file uri and path.
This is code for saving file and works correctly. I can find the file.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
imageCollection = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "application/zip");
contentValues.put(MediaStore.MediaColumns.SIZE, lengthOfFile);
Uri uri = context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), contentValues);
OutputStream outputStream = context.getContentResolver().openOutputStream(uri);
InputStream input = new BufferedInputStream(url.openStream(), 8192);
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
publishProgress("" + (int) ((total * 100) / lengthOfFile));
outputStream.write(data, 0, count);
}
outputStream.flush();
outputStream.close();
input.close();
}
But I cannot open this file later.
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Files.getContentUri("external")
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Files.FileColumns.RELATIVE_PATH,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns._ID
)
val cursor = requireContext().contentResolver.query(collection, projection, null, null, null)
if (cursor!!.getCount() == 0) {
Log.d(TAG, "No file")
} else {
val idColumn=cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH)
while (cursor!!.moveToNext()) {
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val path = cursor.getString(pathColumn)
if (name == "2.1.2.zip") {
val contentUri: Uri = MediaStore.Files.getContentUri("external")
val file= File("content://media/external/file/2.1.2.zip")
if (file.exists()) {
Log.d(TAG, "File not exist")
} else {
Log.d(TAG, "Error")
}
path value return: Download/
name value return: 2.1.2.zip
MediaStore.Files.getContentUri("external"): content://media/external/file
Have any one idea how get access to this file? I try a lot of different way to get file but always get "File not exist".
In our app, There is a camera photo capture feature and its capture images successfully and saved in External storage(/storage/emulated/0/DCIM/Camera/Appname/foldername). But its not showing up in Gallery.
Code we use to save the captured images;
private void saveToPublicStorage(Bitmap bitmap,File file) {
FileOutputStream out = null;
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
out = getFOSForPublicCameraDirectory(file.getName());
} else {
out = new FileOutputStream(file);
}
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
} catch (IOException e) {
ExceptionLogger.logException(e);
}
private FileOutputStream getFOSForPublicCameraDirectory(String fileName) {
FileOutputStream fos = null;
ContentResolver contentResolver = getContentResolver();
Uri imageUri = getURIForPublicCameraDirectory(fileName);
try {
fos = (FileOutputStream) contentResolver.openOutputStream(imageUri);
} catch (Exception e) {
ExceptionLogger.logException(e);
}
return fos;
}
private Uri getURIForPublicCameraDirectory(String fileName) {
ContentResolver contentResolver = 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_DCIM + File.separator + "Camera" + File.separator + "Folder1" + File.separator + "Folder2");
return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
}
Also this same piece of code working in Samsung S20(Android 11).The problem is only with S20.
I have followed https://www.samsung.com/us/support/troubleshooting/TSG01001310/ and checked all the given scenarios and nothing helped.
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?
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.
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) })
}
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()
}
unfortunately the solutions I've found didn't work on android 5.1.1.
I have a bitmap called source. I need to save it directly to my phone's gallery. My manifest contains <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Can you give me a working method to do this?
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()
}
}
}
Also, before calling this, you need to have WRITE_EXTERNAL_STORAGE first.
Use this one:
private void saveImage(Bitmap finalBitmap, String image_name) {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
myDir.mkdirs();
String fname = "Image-" + image_name+ ".jpg";
File file = new File(myDir, fname);
if (file.exists()) file.delete();
Log.i("LOAD", root + fname);
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Use this code this we help you to store images into a particular folder which is saved_images and that folder images show in gallery immediately.
private void SaveImage(Bitmap finalBitmap) {
String root = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".jpg";
File file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
// sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
// Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(this, new String[]{file.toString()}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
}
From Android Q there are changes in saving image to gallery.Thanks to #BaoLei, here is my answer in java if anybody needs it.
private void saveImage(Bitmap bitmap) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
ContentValues values = contentValues();
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + getString(R.string.app_name));
values.put(MediaStore.Images.Media.IS_PENDING, true);
Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
try {
saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri));
values.put(MediaStore.Images.Media.IS_PENDING, false);
this.getContentResolver().update(uri, values, null, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
} else {
File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));
if (!directory.exists()) {
directory.mkdirs();
}
String fileName = System.currentTimeMillis() + ".png";
File file = new File(directory, fileName);
try {
saveImageToStream(bitmap, new FileOutputStream(file));
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
private ContentValues contentValues() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
}
return values;
}
private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Here is a fully working solution in Kotlin:
fun saveToGallery(context: Context, bitmap: Bitmap, albumName: String) {
val filename = "${System.currentTimeMillis()}.png"
val write: (OutputStream) -> Boolean = {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DCIM}/$albumName")
}
context.contentResolver.let {
it.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
it.openOutputStream(uri)?.let(write)
}
}
} else {
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + albumName
val file = File(imagesDir)
if (!file.exists()) {
file.mkdir()
}
val image = File(imagesDir, filename)
write(FileOutputStream(image))
}
}
Do it in One Line
MediaStore.Images.Media.insertImage(applicationContext.getContentResolver(), IMAGE ,"nameofimage" , "description");
Now we have android10 and android11, so here is an updated version, that will work in all android devices.
Make sure you have the WRITE_EXTERNAL_STORAGE permission before calling this function.
private fun saveMediaToStorage(bitmap: Bitmap) {
val filename = "${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
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)
toast("Saved to Photos")
}
}
Its kotlin, and I think very straight forward and self explaining code. But still if you have a problem, comment below and I will explain.
Reference: Android Save Bitmap to Gallery Tutorial.
I'd like to add Java code based on #Bao Lei 's answer that I used in my app.
private void saveImage(Bitmap bitmap, Context context, String folderName) throws FileNotFoundException {
if (android.os.Build.VERSION.SDK_INT >= 29) {
ContentValues values = new 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.
Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
saveImageToStream(bitmap, context.getContentResolver().openOutputStream(uri));
values.put(MediaStore.Images.Media.IS_PENDING, false);
context.getContentResolver().update(uri, values, null, null);
}
} else {
dir = new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"");
// getExternalStorageDirectory is deprecated in API 29
if (!dir.exists()) {
dir.mkdirs();
}
java.util.Date date = new java.util.Date();
imageFile = new File(dir.getAbsolutePath()
+ File.separator
+ new Timestamp(date.getTime()).toString()
+ "Image.jpg");
imageFile = new File(dir.getAbsolutePath()
+ File.separator
+ new Timestamp(date.getTime()).toString()
+ "Image.jpg");
saveImageToStream(bitmap, new FileOutputStream(imageFile));
if (imageFile.getAbsolutePath() != null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, imageFile.getAbsolutePath());
// .DATA is deprecated in API 29
context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
}
}
private ContentValues contentValues() {
ContentValues values = new 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 void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
This one worked fine in my app.
For Media Scanning, you can simply do
val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
intent.data = Uri.fromFile(path) // path must be of File type
context.sendBroadcast(intent)
I was trying to save text file using below code
try {
FileOutputStream fos = new FileOutputStream(TXT_FILE_NAME, true);
FileWriter fWriter;
try {
fWriter = new FileWriter(fos.getFD());
fWriter.write(binding.tvExtractedResult.getText().toString());
fWriter.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
fos.getFD().sync();
fos.close();
Toast.makeText(this, "File Saved Successfully", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(this, "Error while saving file", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
But the problem is this code doesn't work with Android Q.
After this I tried to search the solution and I did this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues myContentValues = new ContentValues();
myContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, TXT_FILE_NAME);
String myFolder = "Download/MY_PROJECT";
myContentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, myFolder);
myContentValues.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
myContentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
Uri extVolumeUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
ContentResolver contentResolver = getContentResolver();
Uri uri = contentResolver.insert(extVolumeUri, myContentValues);
if (uri == null) {
Log.e(TAG, "uri is null");
return;
}
Log.e(TAG, "uri=" + uri);
try {
FileOutputStream fos = new FileOutputStream(new File(uri.toString()));
fos.write(binding.tvExtractedResult.getText().toString().getBytes());
fos.close();
} catch (Exception e) {
Log.e(TAG, "error occurred" + e.getMessage());
e.printStackTrace();
} finally {
myContentValues.clear();
myContentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
contentResolver.update(uri, myContentValues, null, null);
}
}
In above code I'm getting uri == null
Appreciate your help.
Thanks.
Please have a look at this code, it will allow you to save text file below and higher versions of Android Q.
public static void saveFile(Context context, String fileName, String text, String extension) throws IOException{
OutputStream outputStream;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName + extension); // file name
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, DIRECTORY);
Uri extVolumeUri = MediaStore.Files.getContentUri("external");
Uri fileUri = context.getContentResolver().insert(extVolumeUri, values);
outputStream = context.getContentResolver().openOutputStream(fileUri);
}
else {
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).toString() + FOLDER_NAME;
File file = new File(path, fileName + extension);
Log.d(TAG, "saveFile: file path - " + file.getAbsolutePath());
outputStream = new FileOutputStream(file);
}
byte[] bytes = text.getBytes();
outputStream.write(bytes);
outputStream.close();
}
The answer provided by #SheikhHasib is correct.
Here, however, is the Kotlin version of his code with small adjustments. This will save "backup.txt" file in the "Download" directory, and the file will contain "Example content". I've tested in on Android 11 and it works perfectly.
#Throws(IOException::class)
private fun saveFile(context: Context, fileName: String, text: String, extension: String) {
val outputStream: OutputStream? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
val extVolumeUri: Uri = MediaStore.Files.getContentUri("external")
val fileUri: Uri? = context.contentResolver.insert(extVolumeUri, values)
context.contentResolver.openOutputStream(fileUri!!)
} else {
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()
val file = File(path, "$fileName.$extension")
FileOutputStream(file)
}
val bytes = text.toByteArray()
outputStream?.write(bytes)
outputStream?.close()
}
And to run it:
CoroutineScope(Dispatchers.IO).launch {
runCatching{
val content: String = "Example content"
saveFile(requireContext(), "backup", content, "txt")
}
}