5

My goal is to Request picture from Camera app, save it in my app data and also write the same picture to Gallery so if user deletes app it still has the taken pictures (unlike messenger our utility tool pictures are considered highly valuable to user even without the app).

The problem that i'm facing is tightly related to 29 API level witch is the build target (offers to downgrade it not acceptable).

So basically what i get is:

First problem: Even with Manifest.permission.WRITE_EXTERNAL_STORAGE granted I can't access files constructed with Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) i get IOException (permission denied)

Second problem: contentResolver.insert(contentUri, contentValues) throws me this on pre Q device (Nokia 8 Android Pie):

java.lang.SecurityException: Permission Denial: writing com.android.providers.media.MediaProvider uri content://media/external/images/media from .... requires android.permission.WRITE_EXTERNAL_STORAGE, or grantUriPermission()

If i Run on Android 10 device (emulator in my case) the option using contentResolver.insert(..) works fine.

Main Question Is how to write picture to android managed gallery folder properly when targeting 29Api level so it would work on pre android 10 devices?

Extra references

My code for writing picture from my App cache storage to gallery for android 10 is:

    fun writePictureToGalleryQ(context: Context, pictureUri: Uri, pictureName: String) {

        val contentResolver = context.contentResolver

        val relativeLocation = "${Environment.DIRECTORY_PICTURES}${File.separator}WaiJuDuDisAndroid"

        val contentValues  = ContentValues().apply {
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, pictureName)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativeLocation)
        }

        val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        var uri: Uri? = null

        uri = contentResolver.insert(contentUri, contentValues)
        uri?.let { galleryPartUri ->

            val outStream: OutputStream = contentResolver.openOutputStream(galleryPartUri)!!
            val inStream: InputStream = contentResolver.openInputStream(pictureUri)!!

            outStream.use {out ->
                inStream.use { inpt ->
                    inpt.copyTo(out)
                }
            }
        }
    }

P.s. Content providers are set up in manifest as well as needed permissions and file paths xml.

Code that should do for per Q android versions, but it doesn't looks like this (Assuming that i already got Manifest.permission.WRITE_EXTERNAL_STORAGE)

    fun writePictureToGalleryLegacy(context: Context, pictureUri: Uri, pictureName: String) {


        val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)

        val correctDir = File("${directory.absolutePath}${File.separator}WaiJuDuDisAndroid")
        correctDir.mkdirs()

        val file = File("${correctDir.absolutePath}${File.separator}${pictureUri.lastPathSegment}")

        val contentResolver = context.contentResolver

        val outStream: OutputStream = file.outputStream()
        val inStream: InputStream = contentResolver.openInputStream(pictureUri)!!

        outStream.use {out ->
            inStream.use { inpt ->
                inpt.copyTo(out)
            }
        }


        val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension)

        MediaScannerConnection.scanFile(
                context,
                arrayOf(file.absolutePath),
                arrayOf(mimeType?:"image/*"),
                null
        )

    }

Update on issue and edit

I tweaked my code chunks to be a bit more correct since they actually works.

Rebuilding project solved my issue in the end. Not sure what i hate more - Google or myself.

Of course rebuilding project was only part of the solution.

I was having two problems:

  1. Stated by some guy here who removed his answer was that i might be lacking <application... android:requestLegacyExternalStorage="true" ....>
  2. Some library in my dependencies has android:maxSdkVersion in it's manifest that basically causes to ignore my permission on both Android 10 and Android 9 devices and probably everything starting with Android 4.4.

So accordingly i changed my app manifest:

<manifest>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace"/>

    <application android:requestLegacyExternalStorage="true" ...>
        ...
    </application>

</manifest>
Alpha
  • 1,754
  • 3
  • 21
  • 39
  • 2
    https://proandroiddev.com/working-with-scoped-storage-8a7e7cafea3 – blackapps Dec 31 '19 at 11:48
  • For the rest it is unclear if you have problems with Q or with before Q. – blackapps Dec 31 '19 at 11:51
  • With Q I have no problems. But when i try to install same build to P. Even with `android:requestLegacyExternalStorage="true"` i get `open failed: EACCES (Permission denied)` when building file path with `Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)` – Alpha Dec 31 '19 at 12:05
  • You can use that path below Q with the right permission. It us unclear for below Q if you want to use MediaStore to insert a file or wanna do without. And why? – blackapps Dec 31 '19 at 12:13
  • Bellow Q i'm trying to write picture directly to file "/storage/emulated/0/Pictures/PIC_1577795479840.jpeg" But i get Exception with comment "permission denied". what i want to do - i want to save picture in what ever way possible to android pictures directory or DCIM. – Alpha Dec 31 '19 at 12:36
  • Below Q with WRITE permission that should work. Do not use creatNewFile. – blackapps Dec 31 '19 at 12:48
  • Yeah it should but somehow reality is a bit different. Yes `createNewFile()` is unnecessary since `openOutputStream` creates new file either way. I was just testing with that since i used longer path and i usually don't check if `file.mkDirs()` succeeded so when it thrown exception that file doesn't exist i added `createNewFile()`. – Alpha Dec 31 '19 at 13:56
  • @blackapps everything works. Combination of legacy things and library overriding manifest. and on top of that rebuilding and remove/install app was necessary for these changes to work... Not sure if i'm more angry on Google or my self now. – Alpha Jan 02 '20 at 07:23

1 Answers1

0

Had the same problem, will extend the solution for Cordova apps, what worked finally for me is to add this line to config.xml:

<platform name="android">
   <custom-preference name="android-manifest/application/@android:requestLegacyExternalStorage" value="true" />
Anton Eregin
  • 8,140
  • 1
  • 12
  • 14