I am trying to copy a file from internal storage to external storage (SD card) in my Android application. I have the source file in a File object and the destination folder in a String, but I keep getting the error "java.io.FileNotFoundException: open failed: EACCES (Permission denied)".
I am trying to make a gallery, when I select the copy button, it launches an activity where an album is selected and the path is returned as follows as an extra:
var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
val data: Intent? = result.data
val ruta: String = data?.getStringExtra("RUTA")!!
Toast.makeText(this, ruta, Toast.LENGTH_SHORT).show()
}
}
The album path is in this format: /storage/4329-1A0A/DCIM/Facebook
I have tried many different ways to copy the file, I have tried with MediaStore, Files.copy(), File.copy, shell commands, I have the following permissions in the manifest.xml and these permissions are requested at the start of the app:
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.MANAGE_MEDIA"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS"
tools:ignore="ProtectedPermissions" />
This is the way I am trying to copy with shell:
fun copyFileWithShell(context: Context, src: File, dst: File) {
try {
val command = "cp ${src.absolutePath} ${dst.absolutePath}"
val process = Runtime.getRuntime().exec(command)
process.waitFor()
Toast.makeText(context, "File copied successfully", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(context, "Failed to copy file", Toast.LENGTH_SHORT).show()
}
}
This code gives me the error W/System.err: java.io.FileNotFoundException: open failed: EACCES (Permission denied)
fun copyOrMoveImage(operation: String, source: File, destination: String) {
try {
val fileInputStream = FileInputStream(source)
val fileOutputStream = FileOutputStream(destination + "/" + source.name)
val buf = ByteArray(1024)
var len: Int
while (fileInputStream.read(buf).also { len = it } > 0) {
fileOutputStream.write(buf, 0, len)
}
fileInputStream.close()
fileOutputStream.close()
if (operation == "MOVE") {
source.delete()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
val ruta: String = data?.getStringExtra("RUTA")!!
val file = File(media!![position].path)
when (OPERATION) {
"MOVE" -> copyOrMoveImage("MOVE", file, ruta)
"COPY" -> copyOrMoveImage("COPY", file, ruta)
}
This is the result of using geExternalFilesDirs():
/storage/emulated/0/Android/data/com.example.photo/files
/storage/emulated/0/
Also I declare android:requestLegacyExternalStorage="true" in the manifest
The targetSDK of the project is 33. The device where I am trying has Android Q.
Using Environment.getExternalStorageDirectory() returns the path of the Internal Storage ("/storage/emulated/0") instead of The SD Card path ("/storage/4329-1A0A/"), the same with getExternalFilesDirs()
Related
I see so many outdated approaches on how to download and share files in Java but couldn't seem to find any good approach on Kotlin.
There are just so many different ways and somehow most of them are not working properly.
I'm using MVVM + Clean Architecture and I simply want to download a file (mp3) from URL and share it with WhatsApp.
Here is what I found so far for Kotlin (just the download part):
override suspend fun downloadSound(soundURL: String) {
val storage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
withContext(Dispatchers.IO) {
URL(soundURL).openStream().use { input ->
FileOutputStream(File(storage.absolutePath, "shareFile.mp3")).use { output ->
input.copyTo(output)
}
}
}
}
This used to work fine but out of a sudden I'm getting an error on Android 11+:
java.io.FileNotFoundException: /storage/emulated/0/Download/shareFile.mp3: open failed: EACCES (Permission denied)
On Android 10 I eliminated this problem with adding:
android:requestLegacyExternalStorage="true"
to my Manifest.
Here is how I share the sound after downloading:
try {
val storage =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(storage.absolutePath, "shareFile.mp3")
if (file.exists()) {
val uri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
file
)
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.type = "audio/mp3"
context.startActivity(
Intent.createChooser(
intent,
"Share via"
)
)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
Log.i("MeisterTag", "FILE DOES NOT EXIST")
}
I'm also not sure what permissions I need to request, if any.
For now I requested:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
What is the most up-to-date approach that is working for API 23+
I am receiving an object that I want to save with timestamp to text file in the phone's documents folder. I have currently added the permissions lines to AndroidManifest.xml
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Getting it to save .txt to downloads folder. But there is nothing in the file and the size is 0mb.
private fun saveFile() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "device.txt")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
//putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, createFile)
}
private var measureLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Log.d(result.resultCode.toString(), "code ${result.resultCode}")
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
tvPalaute?.text = getString(R.string.status_ok)
val data: Intent? = result.data
if (data != null) {
val measureData = data.getStringExtra("M")
if (measureData != null) {
val date = Date()
Timestamp(date.time)
Log.d(date.toString(), "date")
tvPalaute?.text = getString(R.string.status_ok) + "\n \n" + measureData
}
I have tried following things to save the measureData to text file.
But none of those worked and crashed the application with error something related to file read only.
Caused by: android.system.ErrnoException: open failed: EROFS (Read-only file system)
My app's targetSdkVersion is 30, now I am trying to save image in the gallery. it works in android 11 and android 10 but does not work in android 9 and below versions.
Shows error (permission denial) in android 9 and below versions.
CODE
public fun saveImageToStorage(
mContext: DisplayActivity,
bitmap: Bitmap,
filename: String = "screenshot.jpg",
)
{
try {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, filename)
put(MediaStore.Images.Media.MIME_TYPE, mimeType)
put(MediaStore.Images.Media.RELATIVE_PATH, directory)
}
mContext.contentResolver.run {
val uri =
mContext.contentResolver.insert(mediaContentUri, values)
?: return
imageOutStream = openOutputStream(uri) ?: return
}
imageOutStream.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }
} catch (th: Throwable) {
}
}
AndroidManifest
No permission declared in manifest file
keep using the "old ways" of external storage, by asking permission of external storage for android 9 and below
just add this in manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
MediaStoreCompat from SimpleStorage can handle the backward compatibility:
val fileDesc = FileDescription(fileName, "", "image/jpg")
val mediaFile = MediaStoreCompat.createImage(applicationContext, fileDesc, ImageMediaDirectory.PICTURES)
mediaFile?.openOutputStream()?.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }
This has been asked before but I am unable to find an answer that works well for me.
I have an app in which, at first run, I need to create some directories and files in those directories.
What I have done so far works well on Android 5 to 9.
To my surprise, it does no work on Android 10+ since getExternalStorageDirectory() is deprecated.
This is what I am doing so far:
if(isFirstRun)
{
Log.i("MyApp","ServerWizardActivity > createNecesaryFoldersFiles > isFirstRun")
val absoluteExternalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath()
val appDirectory = File("${absoluteExternalStorageDirectory}/MyApp/")
val picturesDirectory = File("${absoluteExternalStorageDirectory}/Pictures/")
val fullPath: String? = "${absoluteExternalStorageDirectory}/MyApp/${jsonFileName}"
var dataFile = File(fullPath)
if (!appDirectory .exists())
{
Log.i("MyApp","ServerWizardActivity > createNecesaryFoldersFiles > appDirectory")
appDirectory .mkdir()
}
if (!picturesDirectory.exists())
{
Log.i("MyApp","ServerWizardActivity > createNecesaryFoldersFiles > picturesDirectory")
picturesDirectory.mkdir()
}
if(!dataFile.exists())
{
Log.i("MyApp","ServerWizardActivity > createNecesaryFoldersFiles > dataFile")
dataFile.createNewFile()
}
}
else
{
Log.i("MyApp","ServerWizardActivity > createNecesaryFoldersFiles > isNotFirstRun")
}
So far so good.
However, I need to create a directory directly on the Device Storage, on the same level with DCIM, Pictures, Documents and Downloads so that I can make it consistent with Android 5-9 version and the iOS version of the app.
I tried the code below for Android 10+ and it works but I can only create a directory in one of the directories in Environment (in this case, the Pictures directory).
Is there any way to bypass? I don't want to change the entire app structure because of this.
if(isFirstRun)
{
val resolver = contentResolver
val contentValues = ContentValues()
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + "/MyApp"
)
val path = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues).toString()
val folder = File(path)
val isCreated = folder.exists()
if (!isCreated)
{
folder.mkdirs()
}
}
Oh, and soi far I have these in my Android manifest file:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application
android:requestLegacyExternalStorage="true"
</application>
Try with the following code it will help you to create a test directory inside document directory.
//Make sure don't forget to ask runtime permission of read/write file
private fun writeFile(fileData: String, fileName: String) {
val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
.toString() + "/" + "test"
)
} else {
File(
Environment.getExternalStorageDirectory()
.toString() + "/${Environment.DIRECTORY_DOCUMENTS}/" + "test"
)
}
dir.apply {
if (!this.exists()) this.mkdir()
File(this, "$fileName.txt").apply {
if (!this.exists()) this.createNewFile()
FileOutputStream(this).apply {
write(fileData.toByteArray())
close()
}
}
}
}
i want to install .apk from assets directory.For installing first i copy .apk file from assets directory to internal storage or to sdCard then try to install it but because of unknown app install permission application didn't install
val assetManager: AssetManager = assets
try {
var file = File(path)
file.mkdir()
var outputFile = File(path, "testing_app.apk")
if (outputFile.exists()) {
outputFile.delete()
}
var inputStream: InputStream = assetManager.open("testing_app.apk")
var outputStream: OutputStream
outputStream = FileOutputStream(outputFile)
var byteArray = ByteArray(1024)
while (true) {
val read: Int = inputStream.read(byteArray)
if (read < 0) {
break
}
outputStream.write(byteArray, 0, read)
}
inputStream.close()
outputStream.flush()
outputStream.close()
var intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
Uri.fromFile(File("${path}/testing_app.apk")),
"application/vnd.android.package-archive"
)
startActivity(intent)
} catch (e: Exception) {
Log.d("AppError", e.message)
}
With the current API, you have to indicate this in the AndroidManifest.xml:
<!-- required for installing other packages on Android O -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
... and the user also needs to enable "install from unknown sources", in the settings.