Save files to Downloads via MediaStore on Android 10+ (fix #85)
This commit is contained in:
@@ -14,9 +14,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.fairscan.app.ui.screens.export
|
package org.fairscan.app.ui.screens.export
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -39,6 +44,7 @@ import org.fairscan.app.data.ImageRepository
|
|||||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@@ -184,11 +190,21 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
|
|
||||||
for (file in result.files) {
|
for (file in result.files) {
|
||||||
val saved = if (exportDir == null) {
|
val saved = if (exportDir == null) {
|
||||||
val out = fileManager.copyToExternalDir(file)
|
// No export dir defined -> save to Downloads
|
||||||
filesForMediaScan.add(out)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
SavedItem(out.toUri(), out.name, exportFormat)
|
// Android 10+: use MediaStore API
|
||||||
|
val uri = saveViaMediaStore(context, file, exportFormat)
|
||||||
|
SavedItem(uri, file.name, exportFormat)
|
||||||
|
} else {
|
||||||
|
// Android 8 and 9: use File API
|
||||||
|
// (MediaStore doesn't allow to choose Downloads for Android<10)
|
||||||
|
val out = fileManager.copyToExternalDir(file)
|
||||||
|
filesForMediaScan.add(out)
|
||||||
|
SavedItem(out.toUri(), out.name, exportFormat)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val safFile = copyViaSaf(context, file, exportDir, exportFormat)
|
// Use Storage Access Framework to save to the chosen directory
|
||||||
|
val safFile = saveViaSaf(context, file, exportDir, exportFormat)
|
||||||
SavedItem(safFile.uri, safFile.name ?: file.name, exportFormat)
|
SavedItem(safFile.uri, safFile.name ?: file.name, exportFormat)
|
||||||
}
|
}
|
||||||
savedItems += saved
|
savedItems += saved
|
||||||
@@ -221,7 +237,34 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyViaSaf(
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
private fun saveViaMediaStore(
|
||||||
|
context: Context,
|
||||||
|
source: File,
|
||||||
|
format: ExportFormat
|
||||||
|
): Uri {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, source.name)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, format.mimeType)
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
}
|
||||||
|
|
||||||
|
val collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||||
|
val uri = resolver.insert(collection, values)
|
||||||
|
?: throw IOException("Failed to create MediaStore entry")
|
||||||
|
|
||||||
|
resolver.openOutputStream(uri)?.use { out ->
|
||||||
|
source.inputStream().use { input ->
|
||||||
|
input.copyTo(out)
|
||||||
|
}
|
||||||
|
} ?: throw IOException("Failed to open output stream")
|
||||||
|
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveViaSaf(
|
||||||
context: Context,
|
context: Context,
|
||||||
source: File,
|
source: File,
|
||||||
exportDirUri: Uri,
|
exportDirUri: Uri,
|
||||||
|
|||||||
Reference in New Issue
Block a user