Replacement for getExternalStoragePublicDirectory() API 21-30 - android

My app's minSdkVersion is 21 and targetSdkVersion is 30. I need to save image to external storage (to Pictures folder)
So, here's saveImage function:
fun saveImage(context: Context) {
val fileName = "${System.currentTimeMillis()}.png"
val fileType = "image/png"
val folder = "MyApp"
val imageQuality = 100
val write: (OutputStream) -> Boolean = { outputStream ->
this.compress(Bitmap.CompressFormat.PNG, imageQuality, outputStream)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, fileType)
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/$folder")
}
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_PICTURES)
.toString() + File.separator + folder
val file = File(imagesDir)
if (!file.exists()) {
file.mkdir()
}
val image = File(imagesDir, fileName)
write(FileOutputStream(image))
}
}
Also I have this permission in Manifest file:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
This is working ok, but getExternalStoragePublicDirectory is deprecated in API 29+.
Is there a way to get rid of this deprecated function? Right now I just suppress the warning, but it feels kinda bad.

Related

Store File To External Storage Not Working For Android 10 and above - Android Studio/Kotlin

My app has an extract button that takes the data from text view and paste it in a text file "Ledger.txt". It creates a folder in Mobile internal Storage Root Directory "WaterLedger" and place a Ledger.txt file in it and if the text file is already present it appends the text in the file. The code is only working till android 9 not above.
In Android 10 on pressing Extract button it asks "grant permission to write file in storage" but even if you press Yes it still wont create folder (WaterLerger) and paste/update data in Text File (Ledger.txt)
Here is the code
class WriteFile: AsyncTask<String, Int, String>() {
private val mFolder = "/WaterLedger"
lateinit var folder: File
internal var writeThis = "string to cacheApp.txt"
internal var cacheApptxt = "Ledger.txt"
override fun doInBackground(vararg writethis: String): String? {
val received = writethis[0]
if(received.isNotEmpty()){
writeThis = received
}
folder = File(Environment.getExternalStorageDirectory(),"$mFolder/")
if(!folder.exists() || folder.exists()){
folder.mkdir()
val readME = File(folder, cacheApptxt)
val file = File(readME.path)
val out: BufferedWriter
try {
out = BufferedWriter(FileWriter(file, true), 1024)
out.write("****Ledger Updated On Dated:$currentDate*****\n")
out.newLine()
out.write(writeThis)
out.newLine()
out.close()
Log.d("Output_Success", folder.path)
} catch (e: Exception) {
Log.d("Output_Exception", "$e")
}
}
return folder.path
}
// Driver Code
extract.setOnClickListener {
var pathToFileCreated = ""
val anRW = ReadandWrite().readAndwriteStorage(this, this)
if (anRW) {
pathToFileCreated = WriteFile().execute("$StringBuilder").get()
Log.d("pathToFileCreated", pathToFileCreated)
Toast.makeText(this,"File Saved",android.widget.Toast.LENGTH_LONG).show()
}
This will give you External Pictures directory URI (You can create your desirable directory into it):
private fun createTextURI(): Uri? {
val imageCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
else
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
val fileName = System.currentTimeMillis()
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "$fileName.txt")
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
}
val finalURI = contentResolver.insert(imageCollection, contentValues)
resultUri = finalURI
return finalURI
}
Use this URI for file creation like this:
new File(finalURI.getPath());
And write into this file which you created above. ✌
Make sure you have WRITE_EXTERNAL_STORAGE permission.
Ref. Demo App

android Writing to internal storage is not supported

I am try to take photo in private folder and save to public media store.
val takePictureContract = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
uri?.let { fileUri ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "pic_${System.currentTimeMillis()}")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val mediaUri = contentResolver.insert(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
contentValues
)
mediaUri?.let { mUri ->
contentResolver.openOutputStream(mUri)?.use { os ->
contentResolver.openInputStream(fileUri)?.use { inputStream ->
inputStream.copyTo(os)
}
}
}
}
}
}
fun takePic() {
val currentTimeMillis = System.currentTimeMillis()
val folder = File(filesDir, "images")
val file = File(folder, "pic_${currentTimeMillis}")
folder.mkdirs()
uri = FileProvider.getUriForFile(this, "${packageName}.provider", file)
takePictureContract.launch(uri)
}
But after take photo, I have this error message. What's wrong with my code?
Error message I get is :
java.lang.UnsupportedOperationException: Writing to internal storage is not supported
If change INTERNAL_CONTENT_URI to EXTERNAL_CONTENT_URI, and require permission WRITE_EXTERNAL_STORAGE, I can save photo success.

How to make Gallery app on Andorid 10 + 11 show image saved to external storage

So I have the following code:
fun saveMediaToStorage(bitmap: Bitmap, context: Context) {
val filename = "${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)
}
}
I have the permission in the manifest file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
As well as the attribute:
android:requestLegacyExternalStorage="true"
I see the image saved in the proper location when using a FileExplorer app, but the Gallary app doesn't recognize it.
Does anyone notice something that I've done wrong or missed?

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

Environment.getExternalStorageDirectory() deprecated in API level 29 java

Working on android Java, recently updated SDK to API level 29 now there is a warning shown which states that
Environment.getExternalStorageDirectory() is deprecated in API level 29
My code is
private void saveImage() {
if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
final String folderPath = Environment.getExternalStorageDirectory() + "/PhotoEditors";
File folder = new File(folderPath);
if (!folder.exists()) {
File wallpaperDirectory = new File(folderPath);
wallpaperDirectory.mkdirs();
}
showLoading("Saving...");
final String filepath=folderPath
+ File.separator + ""
+ System.currentTimeMillis() + ".png";
File file = new File(filepath);
try {
file.createNewFile();
SaveSettings saveSettings = new SaveSettings.Builder()
.setClearViewsEnabled(true)
.setTransparencyEnabled(true)
.build();
if(isStoragePermissionGranted() ) {
mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() {
#Override
public void onSuccess(#NonNull String imagePath) {
hideLoading();
showSnackbar("Image Saved Successfully");
mPhotoEditorView.getSource().setImageURI(Uri.fromFile(new File(imagePath)));
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(filepath))));
Intent intent = new Intent(EditImageActivity.this, StartActivity.class);
startActivity(intent);
finish();
}
#Override
public void onFailure(#NonNull Exception exception) {
hideLoading();
showSnackbar("Failed to save Image");
}
});
}
What will be the alternative for this?
Use getExternalFilesDir(), getExternalCacheDir(), or getExternalMediaDirs() (methods on Context) instead of Environment.getExternalStorageDirectory().
Or, modify mPhotoEditor to be able to work with a Uri, then:
Use ACTION_CREATE_DOCUMENT to get a Uri to a location of the user's choosing, or
Use MediaStore, ContentResolver, and insert() to get a Uri for a particular type of media (e.g., an image) — see this sample app that demonstrates doing this for downloading MP4 videos from a Web site
Also, note that your Uri.fromFile with ACTION_MEDIA_SCANNER_SCAN_FILE should be crashing on Android 7.0+ with a FileUriExposedException. On Android Q, only the MediaStore/insert() option will get your content indexed by the MediaStore quickly.
Note that you can opt out of these "scoped storage" changes on Android 10 and 11, if your targetSdkVersion is below 30, using android:requestLegacyExternalStorage="true" in the <application> element of the manifest. This is not a long-term solution, as your targetSdkVersion will need to be 30 or higher sometime in 2021 if you are distributing your app through the Play Store (and perhaps elsewhere).
Get the destPath with the new API call:
String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
For Android Q, you can add android:requestLegacyExternalStorage="true" to your element in the manifest. This opts you into the legacy storage model, and your existing external storage code will work.
<manifest ... >
<!-- This attribute is "false" by default on apps targeting
Android 10 or higher. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
Technically, you only need this once you update your targetSdkVersion to 29. Apps with lower targetSdkVersion values default to opting into legacy storage and would need android:requestLegacyExternalStorage="false" to opt out.
Please use getExternalFilesDir(), getExternalCacheDir() instead of Environment.getExternalStorageDirectory() when you creating file in Android 10.
See below line:
val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
This is a small example how to get URI for a file if you want to take photo using default camera and store it in a DCIM folder (DCIM/app_name/filename.jpg):
Open camera (remember about CAMERA permission):
private var photoURI: Uri? = null
private fun openCamera() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
photoURI = getPhotoFileUri()
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
takePictureIntent.resolveActivity(requireActivity().packageManager)?.also {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
And get URI:
private fun getPhotoFileUri(): Uri {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = "IMG_${timeStamp}.jpg"
var uri: Uri? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = requireContext().contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/app_name/")
}
uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
}
return uri ?: getUriForPreQ(fileName)
}
private fun getUriForPreQ(fileName: String): Uri {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
val photoFile = File(dir, "/app_name/$fileName")
if (photoFile.parentFile?.exists() == false) photoFile.parentFile?.mkdir()
return FileProvider.getUriForFile(
requireContext(),
"ru.app_name.fileprovider",
photoFile
)
}
Don't forget about WRITE_EXTERNAL_STORAGE permission for pre Q and add a FileProvider to AndroidManifest.xml.
And get a result:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_IMAGE_CAPTURE -> {
photoURI?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val thumbnail: Bitmap =
requireContext().contentResolver.loadThumbnail(
it, Size(640, 480), null
)
} else {
// pre Q actions
}
}
}
}
}
This worked for me
Add this line in application tag of manifest file
android:requestLegacyExternalStorage="true"
Example
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:networkSecurityConfig="#xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="#style/AppTheme">
</application>
Target SDK is 29
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
To get internal storage directory without hard coding,
Permissions (For all Android versions). Don't forget to get permissions from the user.
Request all files access
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
Legacy permission for Android 10 (add this to AndroidManifest.xml > application tag).
android:requestLegacyExternalStorage="true"
Get internal storage directory path:
public static String getInternalStorageDirectoryPath(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
return storageManager.getPrimaryStorageVolume().getDirectory().getAbsolutePath();
} else {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
}
This worked
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentResolver?.also { resolver ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, "Image_"+".jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator+ "TestFolder")
}
val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos)
Objects.requireNonNull(fos)
}
}
You could make use of StorageManager & StorageVolume classes
StorageVolume.getPrimaryStorageVolume(): This volume is the same storage device returned by Environment#getExternalStorageDirectory() and Context#getExternalFilesDir(String).
public String myGetExternalStorageDir() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
return getPrimaryStorageVolumeForAndroid11AndAbove();
else
return getPrimaryStorageVolumeBeforeAndroid11();
}
#TargetApi(Build.VERSION_CODES.R)
private String getPrimaryStorageVolumeForAndroid11AndAbove() {
StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
return mySV.getDirectory().getPath();
}
private String getPrimaryStorageVolumeBeforeAndroid11() {
String volumeRootPath = "";
StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
Class<?> storageVolumeClazz = null;
try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getPath = storageVolumeClazz.getMethod("getPath");
volumeRootPath = (String) getPath.invoke(mySV);
} catch (Exception e) {
e.printStackTrace();
}
return volumeRootPath;
}
private fun saveImage(bitmap: Bitmap, name: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = contentResolver
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + "YOUR_FOLDER")
val imageUri =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
val fos = resolver.openOutputStream(imageUri!!)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos!!.flush()
fos.close()
toast("Saved to gallery")
} else {
if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
val imagesDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM
).toString() + File.separator + "YOUR_FOLDER"
if (!file.exists()) {
file.mkdir()
}
val image = File(imagesDir, "$name.png")
val fos = FileOutputStream(image)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()
} else {
// ask for permission
}
}
}
Recently I also face similar kind of issues, Due to my code was large and i don't want to add new functionality in existing code so i just simply change path..
Environment.getExternalStorageDirectory() Replace with
context.getExternalFilesDir(null).getAbsolutePath()
Code
private void saveImage() {
if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
final String folderPath = this.getExternalFilesDir(null).getAbsolutePath() + "/PhotoEditors";
File folder = new File(folderPath);
if (!folder.exists()) {
File wallpaperDirectory = new File(folderPath);
wallpaperDirectory.mkdirs();
}
showLoading("Saving...");
final String filepath=folderPath
+ File.separator + ""
+ System.currentTimeMillis() + ".png";
File file = new File(filepath);
try {
file.createNewFile();
SaveSettings saveSettings = new SaveSettings.Builder()
.setClearViewsEnabled(true)
.setTransparencyEnabled(true)
.build();
if(isStoragePermissionGranted() ) {
mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() {
#Override
public void onSuccess(#NonNull String imagePath) {
hideLoading();
showSnackbar("Image Saved Successfully");
mPhotoEditorView.getSource().setImageURI(Uri.fromFile(new File(imagePath)));
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(filepath))));
Intent intent = new Intent(EditImageActivity.this, StartActivity.class);
startActivity(intent);
finish();
}
#Override
public void onFailure(#NonNull Exception exception) {
hideLoading();
showSnackbar("Failed to save Image");
}
});
}
var tempDir: File = inContext.getExternalFilesDir("/")!!
tempDir = File(tempDir.getAbsolutePath().toString() + "/.IncidentImages/")
tempDir.mkdir()
If you work with XAMARIN and are confused by all this different answers (like me) just follow this example:
var picture = new Java.IO.File(Environment.DirectoryPictures, "fileName");
Took me some time to figure this out.

Categories

Resources