0

What I want to do: After the user chooses a directory/folder (and thus allows the app access), I want to write a bitmap to that directory. However, I get this error when writing that bitmap:

Caused by: java.io.FileNotFoundException: /storage/emulated/0/ChosenFolderByUser/output.jpg: open failed: EPERM (Operation not permitted)

Why does this happen since the user has granted access to files in the chosen directory? Here's my code:

private val dirPickerHandler = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) {
    if (it.data != null) {

        val uri = it.data?.data!!

        contentResolver.takePersistableUriPermission(
            uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        )

        grantUriPermission(
            packageName,
            uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
                    Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
        )

        val path = URLDecoder.decode(uri.toString(), "UTF-8")
        val dPath = cleanPath(path)

        showMessage("Storage location successfully updated to $dPath")

        val mIcon = BitmapFactory.decodeResource(getResources(), R.drawable.image_to_save)
        // Write bitmap to directory chosen by user - Error here:
        File(Environment.getExternalStorageDirectory().toString() + "/" + dPath, "output.jpg").writeBitmap(mIcon, Bitmap.CompressFormat.JPEG, 85)

    } else {
        showMessage(
            getString(R.string.no_directory_selected)
        )
    }
}

companion object {
    fun cleanPath(path: String): String {

        if (path.isEmpty()) {
            return "DCIM/Camera"
        }

        val s = URLDecoder.decode(path, "UTF-8")
        return s.substring(s.lastIndexOf(":") + 1)
    }
}

private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
    outputStream().use { out ->
        bitmap.compress(format, quality, out)
        out.flush()
    }
}
iamhere
  • 1
  • 1
  • 11
  • Can you share the target SDK version used in your application? – Swapnil May 02 '22 at 04:51
  • targetSdk = 31 @Swapnil – iamhere May 03 '22 at 23:57
  • Is the image you are copying to external storage supposed to be shared with other apps? If such is the case you can use media collections APIs for this. This won't require any permissions from the user. – Swapnil May 05 '22 at 05:06

2 Answers2

0

Remove the call for grantUriPermission() as it does not make sense.

Dont try to get a path for an uri.

Dont use the File class for an Uri.

You are not showing where the used output stream comes from.

blackapps
  • 8,011
  • 2
  • 11
  • 25
  • For the used output stream, I used this answer's code: https://stackoverflow.com/a/52019768/17836058 – iamhere May 04 '22 at 00:01
  • thank you for your answer but please do inform me how I should proceed instead with code – iamhere May 04 '22 at 00:30
  • You cannot use the File class. And post code. No links. – blackapps May 04 '22 at 04:23
  • Since I can't use the File class, how can I write the bitmap to a specific directory? in my case – iamhere May 04 '22 at 20:05
  • Well you let the user choose a directory. And in this directory you should create a file to which you can write a bitmap. You cannot write a bitmap to a directory as you ask. – blackapps May 04 '22 at 21:01
  • But with ACTION_CREATE_DOCUMENT you can let the user create a file to which you can write/compress the bitmap. – blackapps May 04 '22 at 21:03
  • You can also use the File class with getExternalStoragePublicDirectory() and a filename. Then use a FileOutputStream to compress the bitmap too. – blackapps May 04 '22 at 21:04
  • All in all three different possibilities and there is yet MediaStore. Its time you try them all. – blackapps May 04 '22 at 21:05
0

You cannot use Environment.getExternalStorageDirectory() for target SDK 31. You should use scope storage. Please read more about it here: https://developer.android.com/about/versions/11/privacy/storage

If you want to use the legacy way, you should have the target SDK 30 (max) and in case it is 30, then you should enable requestLegacyExternalStorage.

Swapnil
  • 1,870
  • 2
  • 23
  • 48
  • Thank you for your answer. Because I want to use target SDK 31, can you please give an example code of scope storage I can use instead of `File(Environment.getExternalStorageDirectory().toString() + "/" + dPath, "output.jpg").writeBitmap(mIcon, Bitmap.CompressFormat.JPEG, 85)` – iamhere May 04 '22 at 09:21