Primary directory Download not allowed for media
Asked Answered
P

3

9

Trying to save a PDF file in downloads directory, but after getExternalStoragePublicDirectory got completely deprecated after Android Q, there is no way to save files in any other location than DCIM or Pictures folder as the following exception got thrown when trying to save file there.

IllegalArgumentException: Primary directory Download not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]

Have the following code.

private fun saveFile(input: ByteArray) {
    val fileName = "myFile.pdf"
    val outputStream = if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
        val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val file = File(directory, fileName)
        FileOutputStream(file)
    } else {
        val resolver = context.contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
            put(MediaStore.MediaColumns.MIME_TYPE, "images/*")
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
        }
        resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let {
            resolver.openOutputStream(it)
        }
    }
    outputStream?.use { stream ->
        stream.write(input)
    }
}

Obviously when changing the path to DIRECTORY_DCIM, everything works as expected, but due to requirements the file should be saved to downloads as previously. Would appreciate any help.

Petard answered 10/2, 2020 at 15:52 Comment(1)
Use something like MediaStore.Download.Media.EXTERNAL_CONTENT_URI to save to Download directory.Saccular
P
16

Wasn't setting the correct Uri for file saving, for downloads it should be

resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)

blackapps Thanks for pointing.

Petard answered 10/2, 2020 at 17:39 Comment(2)
and dont forget contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) for Downloads =)Couthie
How to save csv file like this in download folder after API 29 ?Medrano
C
0

When I need to, I do it this way.

Manifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
    android:maxSdkVersion="29" />
<application
  android:name=".BaseApp"
  android:requestLegacyExternalStorage="true"

IFileManager.kt

interface IFileManager {
  fun copyFile(sourceLocation: File, targetLocation: File)

  fun copyFileToDownload(filePath: String, fileName: String, fileType: String)
}

FileManager.kt

class FileManager @Inject constructor(
    private val context: BaseApp
) : IFileManager {
    override fun copyFile(sourceLocation: File, targetLocation: File) {
        CoroutineScope(Dispatchers.IO).launch {
            runCatching {
                try {
                    val inSTR = FileInputStream(sourceLocation)
                    val out = FileOutputStream(targetLocation)
                    val buf = ByteArray(4096)
                    var len = 0
                    while (true) {
                        val read = inSTR.read(buf)
                        if (read == -1) {
                            break
                        }
                        out.write(buf, 0, read)
                        len += read
                    }
                    inSTR.close()
                    out.close()
                    Log.d("copyFile", "runCatching OK")
                } catch (e: Exception) {
                    Log.d("copyFile", "Exception ${e.message}")
                }
            }
        }
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    override fun copyFileToDownload(filePath: String, fileName: String, fileType: String) {
       try {
           val contentResolver: ContentResolver = context.contentResolver
           val values = ContentValues()
           values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
           values.put(MediaStore.MediaColumns.MIME_TYPE, fileType)
           values.put(MediaStore.MediaColumns.TITLE, fileName)
           values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000)
           values.put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis())
           values.put(MediaStore.MediaColumns.IS_PENDING, 1)
           val mediaUri = contentResolver.insert(
               MediaStore.Downloads.EXTERNAL_CONTENT_URI,
               values
           )
           val bytes: ByteArray = Files.readAllBytes(Paths.get(filePath))
           contentResolver.openOutputStream(mediaUri!!).use { out ->
               out!!.write(bytes)
           }
           values.put(MediaStore.MediaColumns.IS_PENDING, 0)
           values.put(
               MediaStore.MediaColumns.RELATIVE_PATH,
               Environment.DIRECTORY_DOWNLOADS
           )
           contentResolver.update(mediaUri, values, null, null)
           Log.d("copyFileToDownload", "OK: ${values.get(MediaStore.MediaColumns.RELATIVE_PATH)}")
       }catch (e: Exception){
           Log.d("copyFileToDownload", "Exception: ${e.message}")
       }
    }

}

MainActivity.kt

@AndroidEntryPoint
open class MainActivity : ComponentActivity() {
  @Inject
  lateinit var iFileManager: IFileManager
  
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        savePdf(path = "your file path")
   }
   
    private fun savePdf(path: String){
      val result = path.split("/")
      val fileName = result[result.size - 1]
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
          iFileManager.copyFileToDownload(
            filePath = path,
            fileName = fileName,
            fileType = "application/pdf"
          )
      }else{
         val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS+"/$fileName")
          iFileManager.copyFile(
            sourceLocation = File(path),
            targetLocation = dir
          )
      }
    }
   
}

I used daggerHilt to inject the context, Dependency Injection with Hilt

Conakry answered 30/9, 2023 at 19:7 Comment(0)
M
-2

On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage. To protect user privacy, on devices that run Android 11 or higher, the system further restricts your app's access to other apps' private directories.

Request MANAGE_EXTERNAL_STORAGE permission

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
Merlenemerlin answered 9/12, 2021 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.