1

I have a private folder that contains sub folders with images and videos, I need to copy that folder to Environment.DIRECTORY_PICTURES for public

val fodler = getExternalFilesDir("Folder") // contains sub folders with images and videos

val DESTINATION = Environment.getExternalStorageDirectory().toString() + File.separator + Environment.DIRECTORY_PICTURES

copyFileOrDirectory(fodler.absolutePath, DESTINATION)

private fun copyFileOrDirectory(srcDir: String, dstDir: String) {
    try {
        val src: File = File(srcDir)
        val dst: File = File(dstDir, src.name)
        if (src.isDirectory) {
            val files = src.list()
            val filesLength = files.size
            for (i in 0 until filesLength) {
                val src1 = File(src, files[i]).path
                val dst1 = dst.path
                copyFileOrDirectory(src1, dst1)
            }
        } else {
            copyFile(src, dst)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

private fun copyFile(sourceFile: File, destFile: File) {
    if (!destFile.getParentFile().exists()) destFile.getParentFile().mkdirs()
    if (!destFile.exists()) {
        destFile.createNewFile()
    }
    var source: FileChannel? = null
    var destination: FileChannel? = null
    try {
        source = FileInputStream(sourceFile).getChannel()
        destination = FileOutputStream(destFile).getChannel()
        destination.transferFrom(source, 0, source.size())
    } finally {
        if (source != null) {
            source.close()
        }
        if (destination != null) {
            destination.close()
        }
    }
}

now I get the file "folder" (with all its content) in "Pictures" directory, visible to everyone and public, exactly what I need.

This solution works for api levels 23-29 (android:requestLegacyExternalStorage="true" for api level 29 in menifest) but it doesn't work in api level 30 because getExternalStorageDirectory() is deprecated and android:requestLegacyExternalStorage="true" in menifest doesn't work for api level 30

what will be the solution for api level 30 ?

Lev T.
  • 31
  • 3
  • "android:requestLegacyExternalStorage="true" in menifest doesn't work for api level 30" -- it does if your `targetSdkVersion` is below 30. The Play Store will require 30 later this year, though. "what will be the solution for api level 30 ?" -- either use `ACTION_OPEN_DOCUMENT_TREE` and let the user choose where the content should be copied, or use `MediaStore`. – CommonsWare Apr 10 '21 at 23:07
  • @CommonsWare Thanks for the quick answer, I have tried to use both ACTION_OPEN_DOCUMENT_TREE and MediaStore but from some reason I always get the same Exception : java.io.IOException: No such file or directory. I would be very grateful if you could give a working code example – Lev T. Apr 10 '21 at 23:38
  • "I always get the same Exception" -- perhaps ask separate Stack Overflow questions for each, providing a [mcve] and including the complete stack trace of the exception. "I would be very grateful if you could give a working code example" -- sorry, but I do not have code samples for these specific scenarios. I have [this 14 blog post series](https://commonsware.com/blog/2019/10/19/scoped-storage-stories-saf-basics.html) on the general topics, though. – CommonsWare Apr 10 '21 at 23:42
  • @CommonsWare Thanks for directing me to use ACTION_OPEN_DOCUMENT_TREE, now it works fine – Lev T. Apr 11 '21 at 15:35

2 Answers2

1

I don't know how to use kotlin to show you an example but, if your problem is what in sdk < 29 you can't use getExternalStorageDirectory() is deprecated maybe you should use MediaStore to save files (images):

private void saveImage(Bitmap bitmap) {
    if (android.os.Build.VERSION.SDK_INT >= 29) {
        ContentValues values = contentValues(); //define your content values to save data
        values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "Example Folder"); //here your path 
        values.put(MediaStore.Images.Media.IS_PENDING, true);
        Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); //this to insert data 
        if (uri != null) {
            try {
                saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri)); //so save the image file
                values.put(MediaStore.Images.Media.IS_PENDING, false);
                Toast.makeText(this, "It works!!", Toast.LENGTH_SHORT).show();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    } else { //and if sdk < 29 
        File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));
        if (!directory.exists()) {
            directory.mkdirs();
        }
        String fileName = "file" + ".jpg";
        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() { //here we do the some values to save in sdk>=29
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "fileImage" + ".jpg");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
    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) { //and this to give a format 
if (outputStream != null) {
    try {
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
1

For api 30 devices you can just copy your folder to the Pictures directory as always.

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) works so use it.

But.. the files need to be pictures.

For api 29 request legacy external storage in application tag of manifest file to copy to public Pictures directory.

blackapps
  • 8,011
  • 2
  • 11
  • 25